21edd3db2b0b48fa593d9690bae57544afc56c85
[automanga.git] / manga / lib.py
1 class library(object):
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
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
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")
24
25 class pagetree(object):
26     """Base class for objects in the tree of pages and pagelists.
27
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
32     information for the pair following it. The only objects with empty
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
39     parent.
40
41     All pagetree objects should contain an attribute `name',
42     containing some human-readable Unicode representation of the
43     pagelist."""
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:
49             return []
50         return self.stack[-1][0].idlist() + [self.id]
51
52     def byidlist(self, idlist):
53         if len(idlist) == 0:
54             return self
55         return self.byid(idlist[0]).byidlist(idlist[1:])
56
57 class pagelist(pagetree):
58     """Class representing a list of either pages, or nested
59     pagelists. Might be, for instance, a volume or a chapter."""
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()
76
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
89 class manga(pagelist):
90     """Class reprenting a single manga. Includes the pagelist class,
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."""
96     pass
97
98 class page(pagetree):
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
112 class 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
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."""
122
123     def __enter__(self):
124         return self
125
126     def __exit__(self, *exc_info):
127         self.close()
128
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
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()
143
144 class cursor(object):
145     def __init__(self, ob):
146         if isinstance(ob, cursor):
147             self.cur = ob.cur
148         else:
149             self.cur = self.descend(ob)
150
151     def descend(self, ob, last=False):
152         while isinstance(ob, pagelist):
153             ob = ob[len(ob) - 1 if last else 0]
154         if not isinstance(ob, page):
155             raise TypeError("object in page tree was unexpectedly not a pagetree")
156         return ob
157
158     def next(self):
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):
166         for n, i in reversed(self.cur.stack):
167             if i > 0:
168                 self.cur = self.descend(n[i - 1], True)
169                 return self.cur
170         raise StopIteration()
171
172     def __iter__(self):
173         return self
174
175 loaded = {}
176 def 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]