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