Added basic profile handling.
[automanga.git] / manga / profile.py
1 import os
2 pj = os.path.join
3
4 home = os.getenv("HOME")
5 if home is None or not os.path.isdir(home):
6     raise Exception("Could not find home directory for profile keeping")
7 basedir = pj(home, ".manga", "profiles")
8
9 def openwdir(nm, mode="r"):
10     if os.path.exists(nm):
11         return open(nm, mode)
12     if mode != "r":
13         d = os.path.dirname(nm)
14         if not os.path.isdir(d):
15             os.makedirs(d)
16     return open(nm, mode)
17
18 def splitline(line):
19     def bsq(c):
20         if c == "\\": return "\\"
21         elif c == '"': return '"'
22         elif c == " ": return " "
23         elif c == "n": return "\n"
24         else: return ""
25     ret = []
26     p = 0
27     buf = ""
28     a = False
29     while p < len(line):
30         c = line[p]
31         if c.isspace():
32             p += 1
33         else:
34             while p < len(line):
35                 c = line[p]
36                 p += 1
37                 if c == '"':
38                     a = True
39                     while p < len(line):
40                         c = line[p]
41                         p += 1
42                         if c == '"':
43                             break
44                         elif c == "\\" and p < len(line):
45                             buf += bsq(line[p])
46                             p += 1
47                         else:
48                             buf += c
49                 elif c.isspace():
50                     ret.append(buf)
51                     buf = ""
52                     break
53                 elif c == "\\" and p < len(line):
54                     buf += bsq(line[p])
55                     p += 1
56                 else:
57                     buf += c
58     if a or buf != "":
59         ret.append(buf)
60     return ret
61
62 def consline(*words):
63     buf = ""
64     for w in words:
65         if any((c == "\\" or c == '"' or c == "\n" for c in w)):
66             wb = ""
67             for c in w:
68                 if c == "\\": wb += "\\\\"
69                 elif c == '"': wb += '\\"'
70                 elif c == "\n": wb += "\\n"
71                 else: wb += c
72             w = wb
73         if w == "" or any((c.isspace() for c in w)):
74             w = '"' + w + '"'
75         if buf != "":
76             buf += " "
77         buf += w
78     return buf
79
80 class manga(object):
81     def __init__(self, profile, libnm, id, path):
82         self.profile = profile
83         self.libnm = libnm
84         self.id = id
85         self.path = path
86         self.props = self.loadprops()
87
88     def loadprops(self):
89         ret = {}
90         with openwdir(self.path) as f:
91             for line in f:
92                 words = splitline(line)
93                 if len(words) < 1: continue
94                 if words[0] == "set" and len(words) > 2:
95                     ret[words[1]] = words[2]
96                 elif words[0] == "lset" and len(words) > 1:
97                     ret[words[1]] = words[2:]
98         return ret
99
100     def prop(self, key, default=KeyError):
101         if key not in self.props:
102             if default is KeyError:
103                 raise KeyError(key)
104             return default
105         return self.props[key]
106
107     def setprop(self, key, val):
108         self.props[key] = val
109
110     def saveprops(self):
111         with openwdir(self.path, "w") as f:
112             for key, val in self.props.iteritems():
113                 if isinstance(val, str):
114                     f.write(consline("set", key, val) + "\n")
115                 else:
116                     f.write(consline("lset", key, *val) + "\n")
117
118     def open(self):
119         import lib
120         return lib.findlib(self.libnm).byid(self.id)
121
122 class profile(object):
123     def __init__(self, dir):
124         self.dir = dir
125         self.name = None
126
127     def getmapping(self):
128         seq = 0
129         ret = {}
130         if os.path.exists(pj(self.dir, "map")):
131             with openwdir(pj(self.dir, "map")) as f:
132                 for ln in f:
133                     words = splitline(ln)
134                     if len(words) < 1:
135                         continue
136                     if words[0] == "seq" and len(words) > 1:
137                         try:
138                             seq = int(words[1])
139                         except ValueError:
140                             pass
141                     elif words[0] == "manga" and len(words) > 3:
142                         try:
143                             ret[words[1], words[2]] = int(words[3])
144                         except ValueError:
145                             pass
146         return seq, ret
147
148     def savemapping(self, seq, m):
149         with openwdir(pj(self.dir, "map"), "w") as f:
150             f.write(consline("seq", str(seq)) + "\n")
151             for (libnm, id), num in m.iteritems():
152                 f.write(consline("manga", libnm, id, str(num)) + "\n")
153
154     def getmanga(self, libnm, id, creat=False):
155         seq, m = self.getmapping()
156         if (libnm, id) in m:
157             return manga(self, libnm, id, pj(self.dir, "%i.manga" % m[(libnm, id)]))
158         if not creat:
159             raise KeyError("no such manga: (%s, %s)" % (libnm, id))
160         while True:
161             try:
162                 fp = openwdir(pj(self.dir, "%i.manga" % seq), "wx")
163             except IOError:
164                 seq += 1
165             else:
166                 break
167         fp.close()
168         m[(libnm, id)] = seq
169         self.savemapping(seq, m)
170         return manga(self, libnm, id, pj(self.dir, "%i.manga" % seq))
171
172     def setlast(self):
173         if self.name is None:
174             raise ValueError("profile at " + self.dir + " has no name")
175         with openwdir(pj(basedir, "last"), "w") as f:
176             f.write(self.name + "\n")
177
178     @classmethod
179     def byname(cls, name):
180         if not name or name == "last" or name[0] == '.':
181             raise KeyError("invalid profile name: " + name)
182         ret = cls(pj(basedir, name))
183         ret.name = name
184         return ret
185
186     @classmethod
187     def last(cls):
188         if not os.path.exists(pj(basedir, "last")):
189             raise KeyError("there is no last used profile")
190         with open(pj(basedir, "last")) as f:
191             return cls.byname(f.readline().strip())