Initial port of core code to Python3.
[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
3cc7937c 9class txfile(object):
6b0254b2
FT
10 def __init__(self, name, mode):
11 self.realname = name
12 self.tempname = name + ".new"
3cc7937c 13 self.bk = open(self.tempname, mode)
6b0254b2
FT
14
15 def close(self, abort=False):
3cc7937c 16 self.bk.close()
6b0254b2
FT
17 if abort:
18 os.unlink(self.tempname)
19 else:
20 os.rename(self.tempname, self.realname)
21
3cc7937c
FT
22 def read(self, sz=-1):
23 return self.bk.read(sz)
24
25 def write(self, data):
26 return self.bk.write(data)
27
6b0254b2
FT
28 def __enter__(self):
29 return self
30
31 def __exit__(self, *exc_info):
32 if exc_info[0] is not None:
33 self.close(True)
34 else:
35 self.close(False)
36
375a030d 37def openwdir(nm, mode="r"):
3cc7937c 38 ft = open
6b0254b2
FT
39 if mode == "W":
40 mode = "w"
41 ft = txfile
375a030d 42 if os.path.exists(nm):
6b0254b2 43 return ft(nm, mode)
375a030d
FT
44 if mode != "r":
45 d = os.path.dirname(nm)
46 if not os.path.isdir(d):
47 os.makedirs(d)
6b0254b2 48 return ft(nm, mode)
375a030d
FT
49
50def splitline(line):
51 def bsq(c):
52 if c == "\\": return "\\"
53 elif c == '"': return '"'
54 elif c == " ": return " "
55 elif c == "n": return "\n"
56 else: return ""
57 ret = []
58 p = 0
59 buf = ""
60 a = False
61 while p < len(line):
62 c = line[p]
63 if c.isspace():
64 p += 1
65 else:
66 while p < len(line):
67 c = line[p]
68 p += 1
69 if c == '"':
70 a = True
71 while p < len(line):
72 c = line[p]
73 p += 1
74 if c == '"':
75 break
76 elif c == "\\" and p < len(line):
77 buf += bsq(line[p])
78 p += 1
79 else:
80 buf += c
81 elif c.isspace():
82 ret.append(buf)
83 buf = ""
012c4cae 84 a = False
375a030d
FT
85 break
86 elif c == "\\" and p < len(line):
87 buf += bsq(line[p])
88 p += 1
89 else:
90 buf += c
91 if a or buf != "":
92 ret.append(buf)
93 return ret
94
f03018e9
FT
95def splitlines(fp):
96 for line in fp:
97 cur = splitline(line)
98 if len(cur) < 1:
99 continue
100 yield cur
101
375a030d
FT
102def consline(*words):
103 buf = ""
104 for w in words:
105 if any((c == "\\" or c == '"' or c == "\n" for c in w)):
106 wb = ""
107 for c in w:
108 if c == "\\": wb += "\\\\"
109 elif c == '"': wb += '\\"'
110 elif c == "\n": wb += "\\n"
111 else: wb += c
112 w = wb
113 if w == "" or any((c.isspace() for c in w)):
114 w = '"' + w + '"'
115 if buf != "":
116 buf += " "
117 buf += w
118 return buf
119
120class manga(object):
43423668 121 def __init__(self, profile, libnm, id):
375a030d
FT
122 self.profile = profile
123 self.libnm = libnm
124 self.id = id
375a030d
FT
125 self.props = self.loadprops()
126
43423668 127 def open(self):
3cc7937c 128 from . import lib
43423668
FT
129 return lib.findlib(self.libnm).byid(self.id)
130
5997ac77
FT
131 def save(self):
132 pass
133
43423668
FT
134class memmanga(manga):
135 def __init__(self, profile, libnm, id):
136 super(memmanga, self).__init__(profile, libnm, id)
137
138 def loadprops(self):
139 return {}
140
f03018e9
FT
141class tagview(object):
142 def __init__(self, manga):
143 self.manga = manga
144 self.profile = manga.profile
145
146 def add(self, *tags):
147 mt = self.getall(self.profile)
148 ctags = mt.setdefault((self.manga.libnm, self.manga.id), set())
149 ctags |= set(tags)
150 self.save(self.profile, mt)
151
152 def remove(self, *tags):
153 mt = self.getall(self.profile)
154 ctags = mt.get((self.manga.libnm, self.manga.id), set())
155 ctags -= set(tags)
156 if len(ctags) < 1:
157 try:
158 del mt[self.manga.libnm, self.manga.id]
159 except KeyError:
160 pass
161 self.save(self.profile, mt)
162
163 def __iter__(self):
164 return iter(self.getall(self.profile).get((self.manga.libnm, self.manga.id), set()))
165
166 @staticmethod
167 def getall(profile):
168 ret = {}
169 try:
170 with profile.file("tags") as fp:
171 for words in splitlines(fp):
172 libnm, id = words[0:2]
173 tags = set(words[2:])
174 ret[libnm, id] = tags
175 except IOError:
176 pass
177 return ret
178
179 @staticmethod
180 def save(profile, m):
6b0254b2 181 with profile.file("tags", "W") as fp:
3cc7937c 182 for (libnm, id), tags in m.items():
f03018e9
FT
183 fp.write(consline(libnm, id, *tags) + "\n")
184
185 @staticmethod
186 def bytag(profile, tag):
187 try:
188 with profile.file("tags") as fp:
189 for words in splitlines(fp):
190 libnm, id = words[0:2]
191 tags = words[2:]
192 if tag in tags:
193 yield profile.getmanga(libnm, id)
194 except IOError:
195 pass
196
43423668
FT
197class filemanga(manga):
198 def __init__(self, profile, libnm, id, path):
199 self.path = path
200 super(filemanga, self).__init__(profile, libnm, id)
f03018e9 201 self.tags = tagview(self)
43423668 202
375a030d
FT
203 def loadprops(self):
204 ret = {}
205 with openwdir(self.path) as f:
f03018e9 206 for words in splitlines(f):
375a030d
FT
207 if words[0] == "set" and len(words) > 2:
208 ret[words[1]] = words[2]
209 elif words[0] == "lset" and len(words) > 1:
210 ret[words[1]] = words[2:]
211 return ret
212
43423668 213 def save(self):
6b0254b2 214 with openwdir(self.path, "W") as f:
3cc7937c 215 for key, val in self.props.items():
375a030d
FT
216 if isinstance(val, str):
217 f.write(consline("set", key, val) + "\n")
218 else:
219 f.write(consline("lset", key, *val) + "\n")
220
375a030d
FT
221class profile(object):
222 def __init__(self, dir):
223 self.dir = dir
224 self.name = None
225
226 def getmapping(self):
227 seq = 0
228 ret = {}
229 if os.path.exists(pj(self.dir, "map")):
230 with openwdir(pj(self.dir, "map")) as f:
f03018e9 231 for words in splitlines(f):
375a030d
FT
232 if words[0] == "seq" and len(words) > 1:
233 try:
234 seq = int(words[1])
235 except ValueError:
236 pass
237 elif words[0] == "manga" and len(words) > 3:
238 try:
239 ret[words[1], words[2]] = int(words[3])
240 except ValueError:
241 pass
242 return seq, ret
243
244 def savemapping(self, seq, m):
6b0254b2 245 with openwdir(pj(self.dir, "map"), "W") as f:
375a030d 246 f.write(consline("seq", str(seq)) + "\n")
3cc7937c 247 for (libnm, id), num in m.items():
375a030d
FT
248 f.write(consline("manga", libnm, id, str(num)) + "\n")
249
250 def getmanga(self, libnm, id, creat=False):
251 seq, m = self.getmapping()
252 if (libnm, id) in m:
43423668 253 return filemanga(self, libnm, id, pj(self.dir, "%i.manga" % m[(libnm, id)]))
375a030d
FT
254 if not creat:
255 raise KeyError("no such manga: (%s, %s)" % (libnm, id))
256 while True:
257 try:
258 fp = openwdir(pj(self.dir, "%i.manga" % seq), "wx")
259 except IOError:
260 seq += 1
261 else:
262 break
263 fp.close()
264 m[(libnm, id)] = seq
265 self.savemapping(seq, m)
43423668 266 return filemanga(self, libnm, id, pj(self.dir, "%i.manga" % seq))
375a030d
FT
267
268 def setlast(self):
269 if self.name is None:
270 raise ValueError("profile at " + self.dir + " has no name")
6b0254b2 271 with openwdir(pj(basedir, "last"), "W") as f:
375a030d
FT
272 f.write(self.name + "\n")
273
271d68da
FT
274 def getaliases(self):
275 ret = {}
276 if os.path.exists(pj(self.dir, "alias")):
277 with openwdir(pj(self.dir, "alias")) as f:
278 for ln in f:
279 ln = splitline(ln)
280 if len(ln) < 1: continue
281 if ln[0] == "alias" and len(ln) > 3:
282 ret[ln[1]] = ln[2], ln[3]
283 return ret
284
285 def savealiases(self, map):
6b0254b2 286 with openwdir(pj(self.dir, "alias"), "W") as f:
3cc7937c 287 for nm, (libnm, id) in map.items():
271d68da
FT
288 f.write(consline("alias", nm, libnm, id) + "\n")
289
477d3ba0
FT
290 def file(self, name, mode="r"):
291 return openwdir(pj(self.dir, name), mode)
292
271d68da
FT
293 def getalias(self, nm):
294 return self.getaliases()[nm]
295
296 def setalias(self, nm, libnm, id):
297 aliases = self.getaliases()
298 aliases[nm] = libnm, id
299 self.savealiases(aliases)
300
f03018e9
FT
301 def bytag(self, tag):
302 return tagview.bytag(self, tag)
303
375a030d
FT
304 @classmethod
305 def byname(cls, name):
306 if not name or name == "last" or name[0] == '.':
307 raise KeyError("invalid profile name: " + name)
308 ret = cls(pj(basedir, name))
309 ret.name = name
310 return ret
311
312 @classmethod
313 def last(cls):
314 if not os.path.exists(pj(basedir, "last")):
315 raise KeyError("there is no last used profile")
316 with open(pj(basedir, "last")) as f:
317 return cls.byname(f.readline().strip())