local: Treat dots/periods as digits in destructuring directories.
[automanga.git] / manga / local.py
CommitLineData
f72650a7 1import os, pathlib
3cc7937c 2from . import lib
1f51eb58 3
f875978b
FT
4def pdigit(s):
5 for c in s:
6 if c not in "0123456789.":
7 return False
8 return True
9
1f51eb58
FT
10def decode1(nm):
11 ret = []
12 p = 0
13 while p < len(nm):
f875978b 14 if pdigit(nm[p]):
1f51eb58
FT
15 s = p
16 p += 1
f875978b 17 while p < len(nm) and pdigit(nm[p]):
1f51eb58
FT
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:
f875978b 34 if pdigit(part):
1f51eb58
FT
35 ret += [int]
36 else:
37 ret += [part]
38 return ret
39
0d299ac7
FT
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):
f72650a7
FT
49 nmpath = path/"names"
50 if not nmpath.exists():
0d299ac7
FT
51 return {}
52 ret = {}
f72650a7 53 with nmpath.open("r") as fp:
0d299ac7
FT
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
1f51eb58
FT
61class imgstream(lib.imgstream):
62 def __init__(self, path):
f72650a7 63 self.bk = path.open("rb")
1f51eb58
FT
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):
f72650a7
FT
116 path = path.resolve()
117 if not path.is_dir():
1f51eb58
FT
118 raise IOError("No such directory: " + path)
119 self.path = path
f72650a7 120 self.id = os.fspath(path)
1f51eb58 121 self.stack = []
f72650a7
FT
122 if (self.path/"name").exists():
123 with (self.path/"name").open("r") as s:
3cc7937c 124 self.name = s.readline().strip()
1f51eb58 125 else:
f72650a7 126 self.name = path.name
1f51eb58
FT
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):
f72650a7
FT
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()]
1f51eb58 139 else:
f72650a7 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]
1f51eb58
FT
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)
fab05388
FT
157 if mx is None:
158 raise TypeError("could not figure out any structure")
1f51eb58
FT
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):
f875978b 164 structs.sort(key=lambda o: o[1][i])
0d299ac7 165 readnames = prefixes(self.path)
1f51eb58
FT
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):])
f72650a7 171 pages.append(page(self, self.path/orig[nm], id, id, p.stack + [(p, len(pages))]))
1f51eb58
FT
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:
afd66b91
FT
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]:])
f72650a7 187 ret.append(page(self, self.path/orig[nm], id, id, p.stack + [(p, len(ret))]))
afd66b91 188 else:
0d299ac7
FT
189 name = findname(readnames, [nm for (nm, st) in sub]) or id
190 cur = interm(name, id, p.stack + [(p, len(ret))], [])
afd66b91
FT
191 cur.direct = constree(cur, sub, idx + 1)
192 ret.append(cur)
1f51eb58
FT
193 return ret
194 return constree(self, structs, 0)
195
196class dumb(lib.library):
197 def byid(self, id):
f72650a7
FT
198 path = pathlib.Path(id)
199 if not path.is_dir():
1f51eb58 200 raise KeyError(id)
f72650a7 201 return manga(path)
1f51eb58
FT
202
203class directory(dumb):
204 def __init__(self, path):
f72650a7 205 if not path.is_dir():
1f51eb58
FT
206 raise IOError("No such directory: " + path)
207 self.path = path
208
209 def byname(self, prefix):
210 ret = []
211 prefix = prefix.lower()
f72650a7
FT
212 for dent in self.path.iterdir():
213 if dent.name[:len(prefix)].lower() == prefix:
214 ret.append(manga(dent))
1f51eb58
FT
215 return ret
216
ffd12e71
FT
217 def search(self, expr):
218 expr = expr.lower()
f72650a7 219 return [manga(dent) for dent in self.path.iterdir() if expr in dent.name.lower()]
ffd12e71 220
1f51eb58 221 def __iter__(self):
f72650a7
FT
222 for dent in self.path.iterdir():
223 yield manga(dent)
1f51eb58 224
ffd12e71 225
1f51eb58 226library = dumb