local: Use pathlib instead of direct filesystem calls.
[automanga.git] / manga / local.py
CommitLineData
f72650a7 1import os, pathlib
3cc7937c 2from . import lib
1f51eb58
FT
3
4def 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
25def 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
0d299ac7
FT
34def 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
42def prefixes(path):
f72650a7
FT
43 nmpath = path/"names"
44 if not nmpath.exists():
0d299ac7
FT
45 return {}
46 ret = {}
f72650a7 47 with nmpath.open("r") as fp:
0d299ac7
FT
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
1f51eb58
FT
55class imgstream(lib.imgstream):
56 def __init__(self, path):
f72650a7 57 self.bk = path.open("rb")
1f51eb58
FT
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
66class 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
77class 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
90def 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
106class manga(lib.manga):
107 exts = ["jpg", "jpeg", "png", "gif"]
108
109 def __init__(self, path):
f72650a7
FT
110 path = path.resolve()
111 if not path.is_dir():
1f51eb58
FT
112 raise IOError("No such directory: " + path)
113 self.path = path
f72650a7 114 self.id = os.fspath(path)
1f51eb58 115 self.stack = []
f72650a7
FT
116 if (self.path/"name").exists():
117 with (self.path/"name").open("r") as s:
3cc7937c 118 self.name = s.readline().strip()
1f51eb58 119 else:
f72650a7 120 self.name = path.name
1f51eb58
FT
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):
f72650a7
FT
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()]
1f51eb58 133 else:
f72650a7 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]
1f51eb58
FT
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)
fab05388
FT
151 if mx is None:
152 raise TypeError("could not figure out any structure")
1f51eb58
FT
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]))
0d299ac7 159 readnames = prefixes(self.path)
1f51eb58
FT
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):])
f72650a7 165 pages.append(page(self, self.path/orig[nm], id, id, p.stack + [(p, len(pages))]))
1f51eb58
FT
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:
afd66b91
FT
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]:])
f72650a7 181 ret.append(page(self, self.path/orig[nm], id, id, p.stack + [(p, len(ret))]))
afd66b91 182 else:
0d299ac7
FT
183 name = findname(readnames, [nm for (nm, st) in sub]) or id
184 cur = interm(name, id, p.stack + [(p, len(ret))], [])
afd66b91
FT
185 cur.direct = constree(cur, sub, idx + 1)
186 ret.append(cur)
1f51eb58
FT
187 return ret
188 return constree(self, structs, 0)
189
190class dumb(lib.library):
191 def byid(self, id):
f72650a7
FT
192 path = pathlib.Path(id)
193 if not path.is_dir():
1f51eb58 194 raise KeyError(id)
f72650a7 195 return manga(path)
1f51eb58
FT
196
197class directory(dumb):
198 def __init__(self, path):
f72650a7 199 if not path.is_dir():
1f51eb58
FT
200 raise IOError("No such directory: " + path)
201 self.path = path
202
203 def byname(self, prefix):
204 ret = []
205 prefix = prefix.lower()
f72650a7
FT
206 for dent in self.path.iterdir():
207 if dent.name[:len(prefix)].lower() == prefix:
208 ret.append(manga(dent))
1f51eb58
FT
209 return ret
210
ffd12e71
FT
211 def search(self, expr):
212 expr = expr.lower()
f72650a7 213 return [manga(dent) for dent in self.path.iterdir() if expr in dent.name.lower()]
ffd12e71 214
1f51eb58 215 def __iter__(self):
f72650a7
FT
216 for dent in self.path.iterdir():
217 yield manga(dent)
1f51eb58 218
ffd12e71 219
1f51eb58 220library = dumb