PageRenderTime 148ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/pygame2/resources.py

https://bitbucket.org/pcraven/pygame2
Python | 318 lines | 284 code | 9 blank | 25 comment | 9 complexity | da214d7adb6519fb7b25189b62730e68 MD5 | raw file
  1. """
  2. Resource management methods.
  3. """
  4. import sys
  5. import os
  6. import re
  7. import zipfile
  8. import tarfile
  9. import io
  10. __all__ = ["open_zipfile", "open_tarfile", "open_url", "Resources"]
  11. # Python 3.x workarounds for the changed urllib modules.
  12. if sys.version_info[0] >= 3:
  13. import urllib.parse as urlparse
  14. import urllib.request as urllib2
  15. else:
  16. import urlparse
  17. import urllib2
  18. def open_zipfile(archive, filename, directory=None):
  19. """Opens and reads a certain file from a ZIP archive.
  20. Opens and reads a certain file from a ZIP archive. The result is
  21. returned as StringIO stream. filename can be a relative or absolute
  22. path within the ZIP archive. The optional directory argument can be
  23. used to supply a relative directory path, under which filename will
  24. be searched.
  25. If the filename could not be found, a KeyError will be raised.
  26. Raises a TypeError, if archive is not a valid ZIP archive.
  27. """
  28. data = None
  29. opened = False
  30. if not isinstance(archive, zipfile.ZipFile):
  31. if not zipfile.is_zipfile(archive):
  32. raise TypeError("passed file does not seem to be a ZIP archive")
  33. else:
  34. archive = zipfile.ZipFile(archive, 'r')
  35. opened = True
  36. apath = filename
  37. if directory:
  38. apath = "%s/%s" % (directory, filename)
  39. try:
  40. dmpdata = archive.open(apath)
  41. data = io.BytesIO(dmpdata.read())
  42. finally:
  43. if opened:
  44. archive.close()
  45. return data
  46. def open_tarfile(archive, filename, directory=None, ftype=None):
  47. """Opens and reads a certain file from a TAR archive.
  48. Opens and reads a certain file from a TAR archive. The result is
  49. returned as StringIO stream. filename can be a relative or absolute
  50. path within the TAR archive. The optional directory argument can be
  51. used to supply a relative directory path, under which filename will
  52. be searched.
  53. ftype is used to supply additional compression information, in case
  54. the system cannot determine the compression type itself, and can be
  55. either 'gz' for gzip compression or 'bz2' for bzip2 compression.
  56. Note:
  57. If ftype is supplied, the compression mode will be enforced for
  58. opening and reading.
  59. If the filename could not be found or an error occured on reading it,
  60. None will be returned.
  61. Raises a TypeError, if archive is not a valid TAR archive or if type
  62. is not a valid value of ('gz', 'bz2').
  63. """
  64. data = None
  65. opened = False
  66. mode = 'r'
  67. if ftype:
  68. if ftype not in ('gz', 'bz2'):
  69. raise TypeError("invalid TAR compression type")
  70. mode = "r:%s" % ftype
  71. if not isinstance(archive, tarfile.TarFile):
  72. if not tarfile.is_tarfile(archive):
  73. raise TypeError("passed file does not seem to be a TAR archive")
  74. else:
  75. archive = tarfile.open(archive, mode)
  76. opened = True
  77. apath = filename
  78. if directory:
  79. apath = "%s/%s" % (directory, filename)
  80. try:
  81. dmpdata = archive.extractfile(apath)
  82. data = io.BytesIO(dmpdata.read())
  83. finally:
  84. if opened:
  85. archive.close()
  86. return data
  87. def open_url(filename, basepath=None):
  88. """Opens and reads a certain file from a web or remote location.
  89. Opens and reads a certain file from a web or remote location. This
  90. function utilizes the urllib2 module, which means that it is
  91. restricted to the types of remote locations supported by urllib2.
  92. basepath can be used to supply an additional location prefix.
  93. """
  94. url = filename
  95. if basepath:
  96. url = urlparse.urljoin(basepath, filename)
  97. return urllib2.urlopen(url)
  98. class Resources(object):
  99. """The Resources class manages a set of file resources and eases
  100. accessing them by using relative paths, scanning archives
  101. automatically and so on.
  102. """
  103. def __init__(self, path=None, subdir=None, excludepattern=None):
  104. """Creates a new resource container instance.
  105. If path is provided, the resource container will scan the path
  106. and add all found files to itself by invoking
  107. scan(path, subdir, excludepattern).
  108. """
  109. self.files = {}
  110. if path:
  111. self.scan(path, subdir, excludepattern)
  112. def _scanzip(self, filename):
  113. """Scans the passed ZIP archive and indexes all the files
  114. contained by it.
  115. """
  116. if not zipfile.is_zipfile(filename):
  117. raise TypeError("file '%s' is not a valid ZIP archive" % filename)
  118. archname = os.path.abspath(filename)
  119. zipf = zipfile.ZipFile(filename, 'r')
  120. for path in zipf.namelist():
  121. fname = os.path.split(path)[1]
  122. if fname:
  123. self.files[fname] = (archname, 'zip', path)
  124. zipf.close()
  125. def _scantar(self, filename, ftype=None):
  126. """Scans the passed TAR archive and indexes all the files
  127. contained by it.
  128. """
  129. if not tarfile.is_tarfile(filename):
  130. raise TypeError("file '%s' is not a valid TAR archive" % filename)
  131. mode = 'r'
  132. if ftype:
  133. if ftype not in ('gz', 'bz2'):
  134. raise TypeError("invalid TAR compression type")
  135. mode = "r:%s" % ftype
  136. archname = os.path.abspath(filename)
  137. archtype = 'tar'
  138. if ftype:
  139. archtype = 'tar%s' % ftype
  140. tar = tarfile.open(filename, mode)
  141. for path in tar.getnames():
  142. fname = os.path.split(path)[1]
  143. self.files[fname] = (archname, archtype, path)
  144. tar.close()
  145. def add(self, filename):
  146. """Adds a file to the Resources container.
  147. Depending on the file type (determined by the file suffix or name),
  148. the file will be automatically scanned (if it is an archive) or
  149. checked for availability (if it is a stream/network resource).
  150. """
  151. if not os.path.exists(filename):
  152. raise ValueError("invalid file path")
  153. if zipfile.is_zipfile(filename):
  154. self.add_archive(filename)
  155. elif tarfile.is_tarfile(filename):
  156. self.add_archive(filename, 'tar')
  157. else:
  158. self.add_file(filename)
  159. def add_file(self, filename):
  160. """Adds a file to the Resources container.
  161. This will only add the passed file and do not scan an archive or
  162. check a stream for availability.
  163. """
  164. if not os.path.exists(filename):
  165. raise ValueError("invalid file path")
  166. abspath = os.path.abspath(filename)
  167. fname = os.path.split(abspath)[1]
  168. if not fname:
  169. raise ValueError("invalid file path")
  170. self.files[fname] = (None, None, abspath)
  171. def add_archive(self, filename, typehint='zip'):
  172. """Adds an archive file to the Resources container.
  173. This will scan the passed archive and add its contents to the
  174. list of available resources.
  175. """
  176. if not os.path.exists(filename):
  177. raise ValueError("invalid file path")
  178. if typehint == 'zip':
  179. self._scanzip(filename)
  180. elif typehint == 'tar':
  181. self._scantar(filename)
  182. elif typehint == 'tarbz2':
  183. self._scantar(filename, 'bz2')
  184. elif typehint == 'targz':
  185. self._scantar(filename, 'gz')
  186. else:
  187. raise ValueError("unsupported archive type")
  188. def get(self, filename):
  189. """Gets a specific file from the Resources.
  190. Raises a KeyError, if filename could not be found.
  191. """
  192. archive, ftype, pathname = self.files[filename]
  193. if archive:
  194. if ftype == 'zip':
  195. return open_zipfile(archive, pathname)
  196. elif ftype == 'tar':
  197. return open_tarfile(archive, pathname)
  198. elif ftype == 'tarbz2':
  199. return open_tarfile(archive, pathname, ftype='bz2')
  200. elif ftype == 'targz':
  201. return open_tarfile(archive, pathname, ftype='gz')
  202. else:
  203. raise ValueError("unsupported archive type")
  204. dmpdata = open(pathname, 'rb')
  205. data = io.BytesIO(dmpdata.read())
  206. dmpdata.close()
  207. return data
  208. def get_filelike(self, filename):
  209. """Like get(), but tries to return the original file handle, if
  210. possible.
  211. If the passed filename is only available within an archive, a
  212. StringIO instance will be returned.
  213. Raises a KeyError, if filename could not be found.
  214. """
  215. archive, ftype, pathname = self.files[filename]
  216. if archive:
  217. if ftype == 'zip':
  218. return open_zipfile(archive, pathname)
  219. elif ftype == 'tar':
  220. return open_tarfile(archive, pathname)
  221. elif ftype == 'tarbz2':
  222. return open_tarfile(archive, pathname, ftype='bz2')
  223. elif ftype == 'targz':
  224. return open_tarfile(archive, pathname, ftype='gz')
  225. else:
  226. raise ValueError("unsupported archive type")
  227. return open(pathname, 'rb')
  228. def get_path(self, filename):
  229. """Gets the path of the passed filename.
  230. If filename is only available within an archive, a string in
  231. the form 'filename@archivename' will be returned.
  232. Raises a KeyError, if filename could not be found.
  233. """
  234. archive, ftype, pathname = self.files[filename]
  235. if archive:
  236. return '%s@%s' % (pathname, archive)
  237. return pathname
  238. def scan(self, path, subdir=None, excludepattern=None):
  239. """Scans a path and adds all found files to the Resources
  240. container.
  241. Scans a path and adds all found files to the Resources
  242. container. If a file is a supported (ZIP or TAR) archive, its
  243. contents will be indexed and added automatically.
  244. The method will consider the directory part (os.path.dirname) of
  245. the provided path as path to scan, if the path is not a
  246. directory. If subdir is provided, it will be appended to the
  247. path and used as starting point for adding files to the
  248. Resources container.
  249. excludepattern can be a regular expression to skip directories, which
  250. match the pattern.
  251. """
  252. match = None
  253. if excludepattern:
  254. match = re.compile(excludepattern).match
  255. join = os.path.join
  256. add = self.add
  257. abspath = os.path.abspath(path)
  258. if not os.path.exists(abspath):
  259. raise ValueError("invalid path '%s'" % path)
  260. if not os.path.isdir(abspath):
  261. abspath = os.path.dirname(abspath)
  262. if subdir is not None:
  263. abspath = os.path.join(abspath, subdir)
  264. if not os.path.exists(abspath):
  265. raise ValueError("invalid path '%s'" % path)
  266. for (pdir, dirnames, filenames) in os.walk(abspath):
  267. if match and match(pdir) is not None:
  268. continue
  269. for fname in filenames:
  270. add(join(pdir, fname))