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