2     """Class representing a single source of multiple mangas."""
 
   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
 
   9         All libraries should implement this."""
 
  10         raise NotImplementedError()
 
  12     def search(self, string):
 
  13         """Returns an iterable object of mangas in this library that
 
  14         matches the search string in a library-dependent manner. While
 
  15         each library is at liberty to define its own matching
 
  16         criteria, it is probably likely to involve something akin to
 
  17         searching for keywords in the titles of the library.
 
  19         Searching may return very many results and may be slow to
 
  22         Not all libraries need implement this."""
 
  23         raise NotImplementedError()
 
  26         """Returns a previously known manga by its string ID, or
 
  27         raises KeyError if no such manga could be found.
 
  29         All libraries should implement this."""
 
  33         """Return an iterator of all known mangas in this library.
 
  35         Not all libraries need implement this."""
 
  36         raise NotImplementedError("manga.lib.library iterator")
 
  38 class pagetree(object):
 
  39     """Base class for objects in the tree of pages and pagelists.
 
  41     All pagetree objects should contain an attribute `stack',
 
  42     containing a list of pairs. The last pair in the list should be
 
  43     the pagetree object which yielded this pagetree object, along with
 
  44     the index which yielded it. Every non-last pair should be the same
 
  45     information for the pair following it. The only objects with empty
 
  46     `stack' lists should be `manga' objects.
 
  48     All non-root pagetree objects should also contain an attribute
 
  49     `id', which should be a string that can be passed to the `byid'
 
  50     function of its parent node to recover the node. Such string ID
 
  51     should be more persistent than the node's numeric index in the
 
  54     All pagetree objects should contain an attribute `name',
 
  55     containing some human-readable Unicode representation of the
 
  59         """Returns a list of the IDs necessary to resolve this node
 
  60         from the root node."""
 
  61         if len(self.stack) == 0:
 
  63         return self.stack[-1][0].idlist() + [self.id]
 
  65     def byidlist(self, idlist):
 
  68         return self.byid(idlist[0]).byidlist(idlist[1:])
 
  70 class pagelist(pagetree):
 
  71     """Class representing a list of either pages, or nested
 
  72     pagelists. Might be, for instance, a volume or a chapter."""
 
  75         """Return the number of (direct) sub-nodes in this pagelist.
 
  77         All pagelists need to implement this."""
 
  78         raise NotImplementedError()
 
  80     def __getitem__(self, idx):
 
  81         """Return the direct sub-node of the given index in this
 
  82         pagelist. Sub-node indexes are always zero-based and
 
  83         contiguous, regardless of any gaps in the underlying medium,
 
  84         which should be indicated instead by way of the `name'
 
  87         All pagelists need to implement this."""
 
  88         raise NotImplementedError()
 
  91         """Return the direct sub-node of this pagelist which has the
 
  92         given string ID. If none is found, a KeyError is raised.
 
  94         This default method iterates the children of this node, but
 
  95         may be overridden by some more efficient implementation.
 
 102 class manga(pagelist):
 
 103     """Class reprenting a single manga. Includes the pagelist class,
 
 104     and all constraints valid for it.
 
 106     A manga is a root pagetree node, but should also contain an `id'
 
 107     attribute, which can be used to recover the manga from its
 
 108     library's `byid' function."""
 
 111 class page(pagetree):
 
 112     """Class representing a single page of a manga. Pages make up the
 
 113     leaf nodes of a pagelist tree.
 
 115     All pages should contain an attribute `manga', referring back to
 
 116     the containing manga instance."""
 
 119         """Open a stream for the image this page represents. The
 
 120         returned object should be an imgstream class.
 
 122         All pages need to implement this."""
 
 123         raise NotImplementedError()
 
 125 class imgstream(object):
 
 126     """An open image I/O stream for a manga page. Generally, it should
 
 127     be file-like. This base class implements the resource-manager
 
 128     interface for use in `with' statements, calling close() on itself
 
 129     when exiting the with-scope.
 
 131     All imgstreams should contain an attribute `ctype', being the
 
 132     Content-Type of the image being read by the stream, and `clen`,
 
 133     being either an int describing the total number of bytes in the
 
 134     stream, or None if the value is not known in advance."""
 
 139     def __exit__(self, *exc_info):
 
 143         """If reading the imgstream may block, fileno() should return
 
 144         a file descriptor that can be polled. If fileno() returns
 
 145         None, that should mean that reading will not block."""
 
 149         """Close this stream."""
 
 150         raise NotImplementedError()
 
 152     def read(self, sz=None):
 
 153         """Read SZ bytes from the stream, or the entire rest of the
 
 154         stream of SZ is not given."""
 
 155         raise NotImplementedError()
 
 157 class stdimgstream(imgstream):
 
 158     """A standard implementation of imgstream, for libraries which
 
 159     have no particular implementation requirements."""
 
 161     def __init__(self, url, referer=None):
 
 162         import urllib.request
 
 163         headers = {"User-Agent": "automanga/1"}
 
 165             headers["Referer"] = referer
 
 166         req = urllib.request.Request(url, headers=headers)
 
 167         self.bk = urllib.request.urlopen(req)
 
 170             if self.bk.getcode() != 200:
 
 171                 raise IOError("Server error: " + str(self.bk.getcode()))
 
 172             self.ctype = self.bk.info()["Content-Type"]
 
 173             self.clen = int(self.bk.info()["Content-Length"])
 
 180         return self.bk.fileno()
 
 185     def read(self, sz=None):
 
 187             return self.bk.read()
 
 189             return self.bk.read(sz)
 
 191 class cursor(object):
 
 192     def __init__(self, ob):
 
 193         if isinstance(ob, cursor):
 
 196             self.cur = self.descend(ob)
 
 198     def descend(self, ob, last=False):
 
 199         while isinstance(ob, pagelist):
 
 200             ob = ob[len(ob) - 1 if last else 0]
 
 201         if not isinstance(ob, page):
 
 202             raise TypeError("object in page tree was unexpectedly not a pagetree")
 
 206         for n, i in reversed(self.cur.stack):
 
 208                 self.cur = self.descend(n[i + 1])
 
 210         raise StopIteration()
 
 213         for n, i in reversed(self.cur.stack):
 
 215                 self.cur = self.descend(n[i - 1], True)
 
 217         raise StopIteration()
 
 225                 except StopIteration:
 
 233         mod = importlib.import_module(name)
 
 234         if not hasattr(mod, "library"):
 
 235             raise ImportError("module " + name + " is not a manga library")
 
 237     if name not in loaded:
 
 239             loaded[name] = load("manga." + name)
 
 241             loaded[name] = load(name)