Make {,back}space pan between the edges of a zoomed page.
[automanga.git] / manga / profile.py
1 import os
2 pj = os.path.join
3
4 home = os.getenv("HOME")
5 if home is None or not os.path.isdir(home):
6     raise Exception("Could not find home directory for profile keeping")
7 basedir = pj(home, ".manga", "profiles")
8
9 def openwdir(nm, mode="r"):
10     if os.path.exists(nm):
11         return open(nm, mode)
12     if mode != "r":
13         d = os.path.dirname(nm)
14         if not os.path.isdir(d):
15             os.makedirs(d)
16     return open(nm, mode)
17
18 def splitline(line):
19     def bsq(c):
20         if c == "\\": return "\\"
21         elif c == '"': return '"'
22         elif c == " ": return " "
23         elif c == "n": return "\n"
24         else: return ""
25     ret = []
26     p = 0
27     buf = ""
28     a = False
29     while p < len(line):
30         c = line[p]
31         if c.isspace():
32             p += 1
33         else:
34             while p < len(line):
35                 c = line[p]
36                 p += 1
37                 if c == '"':
38                     a = True
39                     while p < len(line):
40                         c = line[p]
41                         p += 1
42                         if c == '"':
43                             break
44                         elif c == "\\" and p < len(line):
45                             buf += bsq(line[p])
46                             p += 1
47                         else:
48                             buf += c
49                 elif c.isspace():
50                     ret.append(buf)
51                     buf = ""
52                     a = False
53                     break
54                 elif c == "\\" and p < len(line):
55                     buf += bsq(line[p])
56                     p += 1
57                 else:
58                     buf += c
59     if a or buf != "":
60         ret.append(buf)
61     return ret
62
63 def consline(*words):
64     buf = ""
65     for w in words:
66         if any((c == "\\" or c == '"' or c == "\n" for c in w)):
67             wb = ""
68             for c in w:
69                 if c == "\\": wb += "\\\\"
70                 elif c == '"': wb += '\\"'
71                 elif c == "\n": wb += "\\n"
72                 else: wb += c
73             w = wb
74         if w == "" or any((c.isspace() for c in w)):
75             w = '"' + w + '"'
76         if buf != "":
77             buf += " "
78         buf += w
79     return buf
80
81 class manga(object):
82     def __init__(self, profile, libnm, id):
83         self.profile = profile
84         self.libnm = libnm
85         self.id = id
86         self.props = self.loadprops()
87
88     def open(self):
89         import lib
90         return lib.findlib(self.libnm).byid(self.id)
91
92     def save(self):
93         pass
94
95 class memmanga(manga):
96     def __init__(self, profile, libnm, id):
97         super(memmanga, self).__init__(profile, libnm, id)
98
99     def loadprops(self):
100         return {}
101
102 class filemanga(manga):
103     def __init__(self, profile, libnm, id, path):
104         self.path = path
105         super(filemanga, self).__init__(profile, libnm, id)
106
107     def loadprops(self):
108         ret = {}
109         with openwdir(self.path) as f:
110             for line in f:
111                 words = splitline(line)
112                 if len(words) < 1: continue
113                 if words[0] == "set" and len(words) > 2:
114                     ret[words[1]] = words[2]
115                 elif words[0] == "lset" and len(words) > 1:
116                     ret[words[1]] = words[2:]
117         return ret
118
119     def save(self):
120         with openwdir(self.path, "w") as f:
121             for key, val in self.props.iteritems():
122                 if isinstance(val, str):
123                     f.write(consline("set", key, val) + "\n")
124                 else:
125                     f.write(consline("lset", key, *val) + "\n")
126
127 class profile(object):
128     def __init__(self, dir):
129         self.dir = dir
130         self.name = None
131
132     def getmapping(self):
133         seq = 0
134         ret = {}
135         if os.path.exists(pj(self.dir, "map")):
136             with openwdir(pj(self.dir, "map")) as f:
137                 for ln in f:
138                     words = splitline(ln)
139                     if len(words) < 1:
140                         continue
141                     if words[0] == "seq" and len(words) > 1:
142                         try:
143                             seq = int(words[1])
144                         except ValueError:
145                             pass
146                     elif words[0] == "manga" and len(words) > 3:
147                         try:
148                             ret[words[1], words[2]] = int(words[3])
149                         except ValueError:
150                             pass
151         return seq, ret
152
153     def savemapping(self, seq, m):
154         with openwdir(pj(self.dir, "map"), "w") as f:
155             f.write(consline("seq", str(seq)) + "\n")
156             for (libnm, id), num in m.iteritems():
157                 f.write(consline("manga", libnm, id, str(num)) + "\n")
158
159     def getmanga(self, libnm, id, creat=False):
160         seq, m = self.getmapping()
161         if (libnm, id) in m:
162             return filemanga(self, libnm, id, pj(self.dir, "%i.manga" % m[(libnm, id)]))
163         if not creat:
164             raise KeyError("no such manga: (%s, %s)" % (libnm, id))
165         while True:
166             try:
167                 fp = openwdir(pj(self.dir, "%i.manga" % seq), "wx")
168             except IOError:
169                 seq += 1
170             else:
171                 break
172         fp.close()
173         m[(libnm, id)] = seq
174         self.savemapping(seq, m)
175         return filemanga(self, libnm, id, pj(self.dir, "%i.manga" % seq))
176
177     def setlast(self):
178         if self.name is None:
179             raise ValueError("profile at " + self.dir + " has no name")
180         with openwdir(pj(basedir, "last"), "w") as f:
181             f.write(self.name + "\n")
182
183     def getaliases(self):
184         ret = {}
185         if os.path.exists(pj(self.dir, "alias")):
186             with openwdir(pj(self.dir, "alias")) as f:
187                 for ln in f:
188                     ln = splitline(ln)
189                     if len(ln) < 1: continue
190                     if ln[0] == "alias" and len(ln) > 3:
191                         ret[ln[1]] = ln[2], ln[3]
192         return ret
193
194     def savealiases(self, map):
195         with openwdir(pj(self.dir, "alias"), "w") as f:
196             for nm, (libnm, id) in map.iteritems():
197                 f.write(consline("alias", nm, libnm, id) + "\n")
198
199     def file(self, name, mode="r"):
200         return openwdir(pj(self.dir, name), mode)
201
202     def getalias(self, nm):
203         return self.getaliases()[nm]
204
205     def setalias(self, nm, libnm, id):
206         aliases = self.getaliases()
207         aliases[nm] = libnm, id
208         self.savealiases(aliases)
209
210     @classmethod
211     def byname(cls, name):
212         if not name or name == "last" or name[0] == '.':
213             raise KeyError("invalid profile name: " + name)
214         ret = cls(pj(basedir, name))
215         ret.name = name
216         return ret
217
218     @classmethod
219     def last(cls):
220         if not os.path.exists(pj(basedir, "last")):
221             raise KeyError("there is no last used profile")
222         with open(pj(basedir, "last")) as f:
223             return cls.byname(f.readline().strip())