85e47b390cfd756c7810b28cbdca02e6973bafd0
[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                     a = False
53                     break
54                 elif c == "\\" and p < len(line):
55                     buf += bsq(line[p])
56                     p += 1
57                 else:
58                     buf += c
59     if a or buf != "":
60         ret.append(buf)
61     return ret
62
63 def splitlines(fp):
64     for line in fp:
65         cur = splitline(line)
66         if len(cur) < 1:
67             continue
68         yield cur
69
70 def consline(*words):
71     buf = ""
72     for w in words:
73         if any((c == "\\" or c == '"' or c == "\n" for c in w)):
74             wb = ""
75             for c in w:
76                 if c == "\\": wb += "\\\\"
77                 elif c == '"': wb += '\\"'
78                 elif c == "\n": wb += "\\n"
79                 else: wb += c
80             w = wb
81         if w == "" or any((c.isspace() for c in w)):
82             w = '"' + w + '"'
83         if buf != "":
84             buf += " "
85         buf += w
86     return buf
87
88 class manga(object):
89     def __init__(self, profile, libnm, id):
90         self.profile = profile
91         self.libnm = libnm
92         self.id = id
93         self.props = self.loadprops()
94
95     def open(self):
96         import lib
97         return lib.findlib(self.libnm).byid(self.id)
98
99     def save(self):
100         pass
101
102 class memmanga(manga):
103     def __init__(self, profile, libnm, id):
104         super(memmanga, self).__init__(profile, libnm, id)
105
106     def loadprops(self):
107         return {}
108
109 class tagview(object):
110     def __init__(self, manga):
111         self.manga = manga
112         self.profile = manga.profile
113
114     def add(self, *tags):
115         mt = self.getall(self.profile)
116         ctags = mt.setdefault((self.manga.libnm, self.manga.id), set())
117         ctags |= set(tags)
118         self.save(self.profile, mt)
119
120     def remove(self, *tags):
121         mt = self.getall(self.profile)
122         ctags = mt.get((self.manga.libnm, self.manga.id), set())
123         ctags -= set(tags)
124         if len(ctags) < 1:
125             try:
126                 del mt[self.manga.libnm, self.manga.id]
127             except KeyError:
128                 pass
129         self.save(self.profile, mt)
130
131     def __iter__(self):
132         return iter(self.getall(self.profile).get((self.manga.libnm, self.manga.id), set()))
133
134     @staticmethod
135     def getall(profile):
136         ret = {}
137         try:
138             with profile.file("tags") as fp:
139                 for words in splitlines(fp):
140                     libnm, id = words[0:2]
141                     tags = set(words[2:])
142                     ret[libnm, id] = tags
143         except IOError:
144             pass
145         return ret
146
147     @staticmethod
148     def save(profile, m):
149         with profile.file("tags", "w") as fp:
150             for (libnm, id), tags in m.iteritems():
151                 fp.write(consline(libnm, id, *tags) + "\n")
152
153     @staticmethod
154     def bytag(profile, tag):
155         try:
156             with profile.file("tags") as fp:
157                 for words in splitlines(fp):
158                     libnm, id = words[0:2]
159                     tags = words[2:]
160                     if tag in tags:
161                         yield profile.getmanga(libnm, id)
162         except IOError:
163             pass
164
165 class filemanga(manga):
166     def __init__(self, profile, libnm, id, path):
167         self.path = path
168         super(filemanga, self).__init__(profile, libnm, id)
169         self.tags = tagview(self)
170
171     def loadprops(self):
172         ret = {}
173         with openwdir(self.path) as f:
174             for words in splitlines(f):
175                 if words[0] == "set" and len(words) > 2:
176                     ret[words[1]] = words[2]
177                 elif words[0] == "lset" and len(words) > 1:
178                     ret[words[1]] = words[2:]
179         return ret
180
181     def save(self):
182         with openwdir(self.path, "w") as f:
183             for key, val in self.props.iteritems():
184                 if isinstance(val, str):
185                     f.write(consline("set", key, val) + "\n")
186                 else:
187                     f.write(consline("lset", key, *val) + "\n")
188
189 class profile(object):
190     def __init__(self, dir):
191         self.dir = dir
192         self.name = None
193
194     def getmapping(self):
195         seq = 0
196         ret = {}
197         if os.path.exists(pj(self.dir, "map")):
198             with openwdir(pj(self.dir, "map")) as f:
199                 for words in splitlines(f):
200                     if words[0] == "seq" and len(words) > 1:
201                         try:
202                             seq = int(words[1])
203                         except ValueError:
204                             pass
205                     elif words[0] == "manga" and len(words) > 3:
206                         try:
207                             ret[words[1], words[2]] = int(words[3])
208                         except ValueError:
209                             pass
210         return seq, ret
211
212     def savemapping(self, seq, m):
213         with openwdir(pj(self.dir, "map"), "w") as f:
214             f.write(consline("seq", str(seq)) + "\n")
215             for (libnm, id), num in m.iteritems():
216                 f.write(consline("manga", libnm, id, str(num)) + "\n")
217
218     def getmanga(self, libnm, id, creat=False):
219         seq, m = self.getmapping()
220         if (libnm, id) in m:
221             return filemanga(self, libnm, id, pj(self.dir, "%i.manga" % m[(libnm, id)]))
222         if not creat:
223             raise KeyError("no such manga: (%s, %s)" % (libnm, id))
224         while True:
225             try:
226                 fp = openwdir(pj(self.dir, "%i.manga" % seq), "wx")
227             except IOError:
228                 seq += 1
229             else:
230                 break
231         fp.close()
232         m[(libnm, id)] = seq
233         self.savemapping(seq, m)
234         return filemanga(self, libnm, id, pj(self.dir, "%i.manga" % seq))
235
236     def setlast(self):
237         if self.name is None:
238             raise ValueError("profile at " + self.dir + " has no name")
239         with openwdir(pj(basedir, "last"), "w") as f:
240             f.write(self.name + "\n")
241
242     def getaliases(self):
243         ret = {}
244         if os.path.exists(pj(self.dir, "alias")):
245             with openwdir(pj(self.dir, "alias")) as f:
246                 for ln in f:
247                     ln = splitline(ln)
248                     if len(ln) < 1: continue
249                     if ln[0] == "alias" and len(ln) > 3:
250                         ret[ln[1]] = ln[2], ln[3]
251         return ret
252
253     def savealiases(self, map):
254         with openwdir(pj(self.dir, "alias"), "w") as f:
255             for nm, (libnm, id) in map.iteritems():
256                 f.write(consline("alias", nm, libnm, id) + "\n")
257
258     def file(self, name, mode="r"):
259         return openwdir(pj(self.dir, name), mode)
260
261     def getalias(self, nm):
262         return self.getaliases()[nm]
263
264     def setalias(self, nm, libnm, id):
265         aliases = self.getaliases()
266         aliases[nm] = libnm, id
267         self.savealiases(aliases)
268
269     def bytag(self, tag):
270         return tagview.bytag(self, tag)
271
272     @classmethod
273     def byname(cls, name):
274         if not name or name == "last" or name[0] == '.':
275             raise KeyError("invalid profile name: " + name)
276         ret = cls(pj(basedir, name))
277         ret.name = name
278         return ret
279
280     @classmethod
281     def last(cls):
282         if not os.path.exists(pj(basedir, "last")):
283             raise KeyError("there is no last used profile")
284         with open(pj(basedir, "last")) as f:
285             return cls.byname(f.readline().strip())