local: Treat dots/periods as digits in destructuring directories.
[automanga.git] / manga / local.py
... / ...
CommitLineData
1import os, pathlib
2from . import lib
3
4def pdigit(s):
5 for c in s:
6 if c not in "0123456789.":
7 return False
8 return True
9
10def 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
31def 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
40def 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
48def 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
61class 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
72class 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
83class 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
96def 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
112class 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
196class 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
203class 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
226library = dumb