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