Added profile support for tagging manga.
[automanga.git] / manga / profile.py
CommitLineData
375a030d
FT
1import os
2pj = os.path.join
3
4home = os.getenv("HOME")
5if home is None or not os.path.isdir(home):
6 raise Exception("Could not find home directory for profile keeping")
7basedir = pj(home, ".manga", "profiles")
8
9def 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
18def 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 = ""
012c4cae 52 a = False
375a030d
FT
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
f03018e9
FT
63def splitlines(fp):
64 for line in fp:
65 cur = splitline(line)
66 if len(cur) < 1:
67 continue
68 yield cur
69
375a030d
FT
70def 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
88class manga(object):
43423668 89 def __init__(self, profile, libnm, id):
375a030d
FT
90 self.profile = profile
91 self.libnm = libnm
92 self.id = id
375a030d
FT
93 self.props = self.loadprops()
94
43423668
FT
95 def open(self):
96 import lib
97 return lib.findlib(self.libnm).byid(self.id)
98
5997ac77
FT
99 def save(self):
100 pass
101
43423668
FT
102class 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
f03018e9
FT
109class 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
43423668
FT
165class filemanga(manga):
166 def __init__(self, profile, libnm, id, path):
167 self.path = path
168 super(filemanga, self).__init__(profile, libnm, id)
f03018e9 169 self.tags = tagview(self)
43423668 170
375a030d
FT
171 def loadprops(self):
172 ret = {}
173 with openwdir(self.path) as f:
f03018e9 174 for words in splitlines(f):
375a030d
FT
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
43423668 181 def save(self):
375a030d
FT
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
375a030d
FT
189class 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:
f03018e9 199 for words in splitlines(f):
375a030d
FT
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:
43423668 221 return filemanga(self, libnm, id, pj(self.dir, "%i.manga" % m[(libnm, id)]))
375a030d
FT
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)
43423668 234 return filemanga(self, libnm, id, pj(self.dir, "%i.manga" % seq))
375a030d
FT
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
271d68da
FT
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
477d3ba0
FT
258 def file(self, name, mode="r"):
259 return openwdir(pj(self.dir, name), mode)
260
271d68da
FT
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
f03018e9
FT
269 def bytag(self, tag):
270 return tagview.bytag(self, tag)
271
375a030d
FT
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())