local: Use pathlib instead of direct filesystem calls.
[automanga.git] / manga / local.py
1 import os, pathlib
2 from . import lib
3
4 def decode1(nm):
5     ret = []
6     p = 0
7     while p < len(nm):
8         if nm[p].isdigit():
9             s = p
10             p += 1
11             while p < len(nm) and nm[p].isdigit():
12                 p += 1
13             ret += [nm[s:p]]
14         elif nm[p].isalpha():
15             s = p
16             p += 1
17             while p < len(nm) and nm[p].isalpha():
18                 p += 1
19             ret += [nm[s:p]]
20         else:
21             ret += [nm[p]]
22             p += 1
23     return ret
24
25 def genstr(s):
26     ret = []
27     for part in s:
28         if part.isdigit():
29             ret += [int]
30         else:
31             ret += [part]
32     return ret
33
34 def findname(names, files):
35     matches = list(names.keys())
36     for f in files:
37         matches = [pfx for pfx in matches if f.startswith(pfx)]
38         if len(matches) < 1: return None
39     matches.sort(key=len, reverse=True)
40     return names[matches[0]]
41
42 def prefixes(path):
43     nmpath = path/"names"
44     if not nmpath.exists():
45         return {}
46     ret = {}
47     with nmpath.open("r") as fp:
48         for line in fp:
49             line = line.strip()
50             p = line.find(' ')
51             if p < 0: continue
52             ret[line[:p]] = line[p + 1:]
53     return ret
54
55 class imgstream(lib.imgstream):
56     def __init__(self, path):
57         self.bk = path.open("rb")
58         self.clen = os.fstat(self.bk.fileno()).st_size
59
60     def close(self):
61         self.bk.close()
62
63     def read(self, sz=None):
64         return self.bk.read(sz)
65
66 class page(lib.page):
67     def __init__(self, manga, path, name, id, stack):
68         self.path = path
69         self.id = id
70         self.name = name
71         self.manga = manga
72         self.stack = stack
73
74     def open(self):
75         return imgstream(self.path)
76
77 class interm(lib.pagelist):
78     def __init__(self, name, id, stack, direct):
79         self.name = name
80         self.id = id
81         self.stack = stack
82         self.direct = direct
83
84     def __len__(self):
85         return len(self.direct)
86
87     def __getitem__(self, n):
88         return self.direct[n]
89
90 def maxstruct(flist):
91     mx = None
92     for dent in flist:
93         s = genstr(decode1(dent))
94         if mx is None:
95             mx = s
96         else:
97             nmx = []
98             for p, n in zip(mx, s):
99                 if p == n:
100                     nmx.append(p)
101                 else:
102                     break
103             mx = nmx
104     return mx
105
106 class manga(lib.manga):
107     exts = ["jpg", "jpeg", "png", "gif"]
108
109     def __init__(self, path):
110         path = path.resolve()
111         if not path.is_dir():
112             raise IOError("No such directory: " + path)
113         self.path = path
114         self.id = os.fspath(path)
115         self.stack = []
116         if (self.path/"name").exists():
117             with (self.path/"name").open("r") as s:
118                 self.name = s.readline().strip()
119         else:
120             self.name = path.name
121         self.direct = self.destruct()
122
123     def __len__(self):
124         return len(self.direct)
125
126     def __getitem__(self, idx):
127         return self.direct[idx]
128
129     def imglist(self):
130         if (self.path/"order").exists():
131             with (self.path/"order").open("r") as s:
132                 return True, [line.strip() for line in s if (self.path/line.strip()).exists()]
133         else:
134             return False, [dent for dent in (dent.name for dent in self.path.iterdir()) if '.' in dent and dent[dent.rindex('.') + 1:] in self.exts]
135
136     def bakenames(self, files):
137         ret = []
138         map = {}
139         for orig in files:
140             nm = orig
141             if '.' in nm:
142                 nm = nm[:nm.rindex('.')]
143             ret.append(nm)
144             map[nm] = orig
145         return ret, map
146
147     def destruct(self):
148         ordered, files = self.imglist()
149         pages, orig = self.bakenames(files)
150         mx = maxstruct(pages)
151         if mx is None:
152             raise TypeError("could not figure out any structure")
153         var = [i for i, part in enumerate(mx) if part == int]
154         structs = [(nm, decode1(nm)) for nm in pages]
155         if not ordered:
156             structs.sort(key=lambda o: "".join(o[1][len(mx):]))
157             for i in reversed(var):
158                 structs.sort(key=lambda o: int(o[1][i]))
159         readnames = prefixes(self.path)
160         def constree(p, structs, idx):
161             if idx == len(var):
162                 pages = []
163                 for nm, st in structs:
164                     id = "".join(st[len(mx):])
165                     pages.append(page(self, self.path/orig[nm], id, id, p.stack + [(p, len(pages))]))
166                 return pages
167             else:
168                 ids = set()
169                 oids = []
170                 for nm, st in structs:
171                     cur = st[var[idx]]
172                     if cur not in ids:
173                         ids.add(cur)
174                         oids.append(cur)
175                 ret = []
176                 for id in oids:
177                     sub = [(nm, st) for nm, st in structs if st[var[idx]] == id]
178                     if len(sub) == 1:
179                         nm, st = sub[0]
180                         id = "".join(st[var[idx]:])
181                         ret.append(page(self, self.path/orig[nm], id, id, p.stack + [(p, len(ret))]))
182                     else:
183                         name = findname(readnames, [nm for (nm, st) in sub]) or id
184                         cur = interm(name, id, p.stack + [(p, len(ret))], [])
185                         cur.direct = constree(cur, sub, idx + 1)
186                         ret.append(cur)
187                 return ret
188         return constree(self, structs, 0)
189
190 class dumb(lib.library):
191     def byid(self, id):
192         path = pathlib.Path(id)
193         if not path.is_dir():
194             raise KeyError(id)
195         return manga(path)
196
197 class directory(dumb):
198     def __init__(self, path):
199         if not path.is_dir():
200             raise IOError("No such directory: " + path)
201         self.path = path
202
203     def byname(self, prefix):
204         ret = []
205         prefix = prefix.lower()
206         for dent in self.path.iterdir():
207             if dent.name[:len(prefix)].lower() == prefix:
208                 ret.append(manga(dent))
209         return ret
210
211     def search(self, expr):
212         expr = expr.lower()
213         return [manga(dent) for dent in self.path.iterdir() if expr in dent.name.lower()]
214
215     def __iter__(self):
216         for dent in self.path.iterdir():
217             yield manga(dent)
218
219
220 library = dumb