Handle mangafox download errors slightly nicer.
[automanga.git] / manga / lib.py
CommitLineData
f3ad0817 1class library(object):
6a1e046b
FT
2 """Class representing a single source of multiple mangas."""
3
4 def byname(self, prefix):
5 """Returns an iterable object of all mangas in this library
6 whose names (case-insensitively) begin with the given
7 prefix.
8
9 All libraries should implement this."""
10 raise NotImplementedError()
11
46b3b90e
FT
12 def byid(self, id):
13 """Returns a previously known manga by its string ID, or
14 raises KeyError if no such manga could be found.
15
16 All libraries should implement this."""
17 raise KeyError(id)
18
6a1e046b
FT
19 def __iter__(self):
20 """Return an iterator of all known mangas in this library.
21
22 Not all libraries need implement this."""
23 raise NotImplementedError("manga.lib.library iterator")
f3ad0817 24
3683ab38
FT
25class pagetree(object):
26 """Base class for objects in the tree of pages and pagelists.
27
46b3b90e
FT
28 All pagetree objects should contain an attribute `stack',
29 containing a list of pairs. The last pair in the list should be
30 the pagetree object which yielded this pagetree object, along with
31 the index which yielded it. Every non-last pair should be the same
3683ab38 32 information for the pair following it. The only objects with empty
46b3b90e
FT
33 `stack' lists should be `manga' objects.
34
35 All non-root pagetree objects should also contain an attribute
36 `id', which should be a string that can be passed to the `byid'
37 function of its parent node to recover the node. Such string ID
38 should be more persistent than the node's numeric index in the
699d0c17
FT
39 parent.
40
41 All pagetree objects should contain an attribute `name',
42 containing some human-readable Unicode representation of the
43 pagelist."""
46b3b90e
FT
44
45 def idlist(self):
46 """Returns a list of the IDs necessary to resolve this node
47 from the root node."""
48 if len(self.stack) == 0:
bc48738d
FT
49 return []
50 return self.stack[-1][0].idlist() + [self.id]
46b3b90e
FT
51
52 def byidlist(self, idlist):
53 if len(idlist) == 0:
54 return self
55 return self.byid(idlist[0]).byidlist(idlist[1:])
3683ab38
FT
56
57class pagelist(pagetree):
6a1e046b 58 """Class representing a list of either pages, or nested
699d0c17 59 pagelists. Might be, for instance, a volume or a chapter."""
6a1e046b
FT
60
61 def __len__(self):
62 """Return the number of (direct) sub-nodes in this pagelist.
63
64 All pagelists need to implement this."""
65 raise NotImplementedError()
66
67 def __getitem__(self, idx):
68 """Return the direct sub-node of the given index in this
69 pagelist. Sub-node indexes are always zero-based and
70 contiguous, regardless of any gaps in the underlying medium,
71 which should be indicated instead by way of the `name'
72 attribute.
73
74 All pagelists need to implement this."""
75 raise NotImplementedError()
f3ad0817 76
46b3b90e
FT
77 def byid(self, id):
78 """Return the direct sub-node of this pagelist which has the
79 given string ID. If none is found, a KeyError is raised.
80
81 This default method iterates the children of this node, but
82 may be overridden by some more efficient implementation.
83 """
84 for ch in self:
85 if ch.id == id:
86 return ch
87 raise KeyError(id)
88
f3ad0817 89class manga(pagelist):
6a1e046b 90 """Class reprenting a single manga. Includes the pagelist class,
46b3b90e
FT
91 and all constraints valid for it.
92
93 A manga is a root pagetree node, but should also contain an `id'
94 attribute, which can be used to recover the manga from its
95 library's `byid' function."""
f3ad0817
FT
96 pass
97
3683ab38 98class page(pagetree):
6a1e046b
FT
99 """Class representing a single page of a manga. Pages make up the
100 leaf nodes of a pagelist tree.
101
102 All pages should contain an attribute `manga', referring back to
103 the containing manga instance."""
104
105 def open(self):
106 """Open a stream for the image this page represents. The
107 returned object should be an imgstream class.
108
109 All pages need to implement this."""
110 raise NotImplementedError()
111
112class imgstream(object):
113 """An open image I/O stream for a manga page. Generally, it should
114 be file-like. This base class implements the resource-manager
115 interface for use in `with' statements, calling close() on itself
116 when exiting the with-scope.
117
118 All imgstreams should contain an attribute `ctype', being the
9948db89
FT
119 Content-Type of the image being read by the stream, and `clen`,
120 being either an int describing the total number of bytes in the
121 stream, or None if the value is not known in advance."""
6a1e046b
FT
122
123 def __enter__(self):
124 return self
125
126 def __exit__(self, *exc_info):
127 self.close()
128
af730068
FT
129 def fileno(self):
130 """If reading the imgstream may block, fileno() should return
131 a file descriptor that can be polled. If fileno() returns
132 None, that should mean that reading will not block."""
133 return None
134
6a1e046b
FT
135 def close(self):
136 """Close this stream."""
137 raise NotImplementedError()
138
139 def read(self, sz = None):
140 """Read SZ bytes from the stream, or the entire rest of the
141 stream of SZ is not given."""
142 raise NotImplementedError()
07be272b 143
055ad3fd
FT
144class cursor(object):
145 def __init__(self, ob):
d8bea304
FT
146 if isinstance(ob, cursor):
147 self.cur = ob.cur
148 else:
149 self.cur = self.descend(ob)
055ad3fd 150
39b66c75 151 def descend(self, ob, last=False):
055ad3fd 152 while isinstance(ob, pagelist):
39b66c75 153 ob = ob[len(ob) - 1 if last else 0]
055ad3fd
FT
154 if not isinstance(ob, page):
155 raise TypeError("object in page tree was unexpectedly not a pagetree")
156 return ob
07be272b
FT
157
158 def next(self):
055ad3fd
FT
159 for n, i in reversed(self.cur.stack):
160 if i < len(n) - 1:
161 self.cur = self.descend(n[i + 1])
162 return self.cur
163 raise StopIteration()
164
165 def prev(self):
f19ac53a 166 for n, i in reversed(self.cur.stack):
055ad3fd 167 if i > 0:
39b66c75 168 self.cur = self.descend(n[i - 1], True)
055ad3fd
FT
169 return self.cur
170 raise StopIteration()
07be272b
FT
171
172 def __iter__(self):
173 return self
53395a9d 174
31ea855c
FT
175loaded = {}
176def findlib(name):
177 def load(name):
178 mod = __import__(name, fromlist=["dummy"])
179 if not hasattr(mod, "library"):
180 raise ImportError("module " + name + " is not a manga library")
181 return mod.library()
182 if name not in loaded:
183 try:
184 loaded[name] = load("manga." + name)
185 except ImportError:
186 loaded[name] = load(name)
187 return loaded[name]