You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

735 lines
26 KiB

  1. # Copyright (c) 2011, SmartFile <btimby@smartfile.com>
  2. # All rights reserved.
  3. #
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions are met:
  6. # * Redistributions of source code must retain the above copyright
  7. # notice, this list of conditions and the following disclaimer.
  8. # * Redistributions in binary form must reproduce the above copyright
  9. # notice, this list of conditions and the following disclaimer in the
  10. # documentation and/or other materials provided with the distribution.
  11. # * Neither the name of the organization nor the
  12. # names of its contributors may be used to endorse or promote products
  13. # derived from this software without specific prior written permission.
  14. #
  15. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  16. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  17. # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
  19. # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  20. # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  21. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  22. # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  23. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  24. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. import os
  26. import stat
  27. import sys
  28. import time
  29. import warnings
  30. # from ctypes import cdll, c_char_p
  31. from libarchive import _libarchive
  32. from io import StringIO
  33. # PY3 = sys.version_info[0] == 3
  34. PY3 = True
  35. # Suggested block size for libarchive. Libarchive may adjust it.
  36. BLOCK_SIZE = 10240
  37. MTIME_FORMAT = ''
  38. # Default encoding scheme.
  39. ENCODING = 'utf-8'
  40. # Functions to initialize read/write for various libarchive supported formats and filters.
  41. FORMATS = {
  42. None: (_libarchive.archive_read_support_format_all, None),
  43. 'tar': (_libarchive.archive_read_support_format_tar, _libarchive.archive_write_set_format_ustar),
  44. 'pax': (_libarchive.archive_read_support_format_tar, _libarchive.archive_write_set_format_pax),
  45. 'gnu': (_libarchive.archive_read_support_format_gnutar, _libarchive.archive_write_set_format_gnutar),
  46. 'zip': (_libarchive.archive_read_support_format_zip, _libarchive.archive_write_set_format_zip),
  47. 'rar': (_libarchive.archive_read_support_format_rar, None),
  48. '7zip': (_libarchive.archive_read_support_format_7zip, None),
  49. 'ar': (_libarchive.archive_read_support_format_ar, None),
  50. 'cab': (_libarchive.archive_read_support_format_cab, None),
  51. 'cpio': (_libarchive.archive_read_support_format_cpio, _libarchive.archive_write_set_format_cpio_newc),
  52. 'iso': (_libarchive.archive_read_support_format_iso9660, _libarchive.archive_write_set_format_iso9660),
  53. 'lha': (_libarchive.archive_read_support_format_lha, None),
  54. 'xar': (_libarchive.archive_read_support_format_xar, _libarchive.archive_write_set_format_xar),
  55. }
  56. FILTERS = {
  57. None: (_libarchive.archive_read_support_filter_all, _libarchive.archive_write_add_filter_none),
  58. 'gz': (_libarchive.archive_read_support_filter_gzip, _libarchive.archive_write_add_filter_gzip),
  59. 'bz2': (_libarchive.archive_read_support_filter_bzip2, _libarchive.archive_write_add_filter_bzip2),
  60. }
  61. # Map file extensions to formats and filters. To support quick detection.
  62. FORMAT_EXTENSIONS = {
  63. '.tar': 'tar',
  64. '.zip': 'zip',
  65. '.rar': 'rar',
  66. '.7z': '7zip',
  67. '.ar': 'ar',
  68. '.cab': 'cab',
  69. '.rpm': 'cpio',
  70. '.cpio': 'cpio',
  71. '.iso': 'iso',
  72. '.lha': 'lha',
  73. '.xar': 'xar',
  74. }
  75. FILTER_EXTENSIONS = {
  76. '.gz': 'gz',
  77. '.bz2': 'bz2',
  78. }
  79. class EOF(Exception):
  80. '''Raised by ArchiveInfo.from_archive() when unable to read the next
  81. archive header.'''
  82. pass
  83. def version():
  84. '''Returns the version of the libarchive library.'''
  85. return _libarchive.archive_version_string().split()[1]
  86. def get_error(archive):
  87. '''Retrieves the last error description for the given archive instance.'''
  88. return _libarchive.archive_error_string(archive)
  89. def call_and_check(func, archive, *args):
  90. '''Executes a libarchive function and raises an exception when appropriate.'''
  91. ret = func(*args)
  92. if ret == _libarchive.ARCHIVE_OK:
  93. return
  94. elif ret == _libarchive.ARCHIVE_WARN:
  95. warnings.warn('Warning executing function: %s.' % get_error(archive), RuntimeWarning)
  96. elif ret == _libarchive.ARCHIVE_EOF:
  97. raise EOF()
  98. else:
  99. raise Exception('Problem executing function, message is: %s.' % get_error(archive))
  100. def get_func(name, items, index):
  101. item = items.get(name, None)
  102. if item is None:
  103. return None
  104. return item[index]
  105. def guess_format(filename):
  106. if isinstance(filename, int):
  107. filename = ext = ''
  108. else:
  109. filename, ext = os.path.splitext(filename)
  110. filter = FILTER_EXTENSIONS.get(ext)
  111. if filter:
  112. filename, ext = os.path.splitext(filename)
  113. format = FORMAT_EXTENSIONS.get(ext)
  114. return format, filter
  115. def is_archive_name(filename, formats=None):
  116. '''Quick check to see if the given file has an extension indiciating that it is
  117. an archive. The format parameter can be used to limit what archive format is acceptable.
  118. If omitted, all supported archive formats will be checked.
  119. This function will return the name of the most likely archive format, None if the file is
  120. unlikely to be an archive.'''
  121. if formats is None:
  122. formats = list(FORMAT_EXTENSIONS.values())
  123. format, filter = guess_format(filename)
  124. if format in formats:
  125. return format
  126. def is_archive(f, formats=(None,), filters=(None,)):
  127. '''Check to see if the given file is actually an archive. The format parameter
  128. can be used to specify which archive format is acceptable. If ommitted, all supported
  129. archive formats will be checked. It opens the file using libarchive. If no error is
  130. received, the file was successfully detected by the libarchive bidding process.
  131. This procedure is quite costly, so you should avoid calling it unless you are reasonably
  132. sure that the given file is an archive. In other words, you may wish to filter large
  133. numbers of file names using is_archive_name() before double-checking the positives with
  134. this function.
  135. This function will return True if the file can be opened as an archive using the given
  136. format(s)/filter(s).'''
  137. need_close: bool = False
  138. if isinstance(f, str):
  139. f = open(f, 'rb')
  140. need_close = True
  141. a = _libarchive.archive_read_new()
  142. for format in formats:
  143. format = get_func(format, FORMATS, 0)
  144. if format is None:
  145. return False
  146. format(a)
  147. for filter in filters:
  148. filter = get_func(filter, FILTERS, 0)
  149. if filter is None:
  150. return False
  151. filter(a)
  152. try:
  153. try:
  154. call_and_check(_libarchive.archive_read_open_fd, a, a, f.fileno(), BLOCK_SIZE)
  155. return True
  156. except:
  157. return False
  158. finally:
  159. _libarchive.archive_read_close(a)
  160. _libarchive.archive_read_free(a)
  161. if need_close:
  162. f.close()
  163. class EntryReadStream(object):
  164. '''A file-like object for reading an entry from the archive.'''
  165. def __init__(self, archive, size):
  166. self.archive = archive
  167. self.closed = False
  168. self.size = size
  169. self.bytes = 0
  170. def __enter__(self):
  171. return self
  172. def __exit__(self, *args):
  173. return
  174. def __iter__(self):
  175. if self.closed:
  176. return
  177. while True:
  178. data = self.read(BLOCK_SIZE)
  179. if not data:
  180. break
  181. yield data
  182. def __len__(self):
  183. return self.size
  184. def tell(self):
  185. return self.bytes
  186. def read(self, bytes=-1):
  187. if self.closed:
  188. return
  189. if self.bytes == self.size:
  190. # EOF already reached.
  191. return
  192. if bytes < 0:
  193. bytes = self.size - self.bytes
  194. elif self.bytes + bytes > self.size:
  195. # Limit read to remaining bytes
  196. bytes = self.size - self.bytes
  197. # Read requested bytes
  198. data = _libarchive.archive_read_data_into_str(self.archive._a, bytes)
  199. self.bytes += len(data)
  200. return data
  201. def close(self):
  202. if self.closed:
  203. return
  204. # Call archive.close() with _defer True to let it know we have been
  205. # closed and it is now safe to actually close.
  206. self.archive.close(_defer=True)
  207. self.archive = None
  208. self.closed = True
  209. class EntryWriteStream(object):
  210. '''A file-like object for writing an entry to an archive.
  211. If the size is known ahead of time and provided, then the file contents
  212. are not buffered but flushed directly to the archive. If size is omitted,
  213. then the file contents are buffered and flushed in the close() method.'''
  214. def __init__(self, archive, pathname, size=None):
  215. self.archive = archive
  216. self.entry = Entry(pathname=pathname, mtime=time.time(), mode=stat.S_IFREG)
  217. if size is None:
  218. self.buffer = StringIO()
  219. else:
  220. self.buffer = None
  221. self.entry.size = size
  222. self.entry.to_archive(self.archive)
  223. self.bytes = 0
  224. self.closed = False
  225. def __enter__(self):
  226. return self
  227. def __exit__(self, *args):
  228. self.close()
  229. def __del__(self):
  230. self.close()
  231. def __len__(self):
  232. return self.bytes
  233. def tell(self):
  234. return self.bytes
  235. def write(self, data):
  236. if self.closed:
  237. raise Exception('Cannot write to closed stream.')
  238. if self.buffer:
  239. self.buffer.write(data)
  240. else:
  241. _libarchive.archive_write_data_from_str(self.archive._a, data.encode('utf-8'))
  242. self.bytes += len(data)
  243. def close(self):
  244. if self.closed:
  245. return
  246. if self.buffer:
  247. self.entry.size = self.buffer.tell()
  248. self.entry.to_archive(self.archive)
  249. _libarchive.archive_write_data_from_str(self.archive._a, self.buffer.getvalue().encode('utf-8'))
  250. _libarchive.archive_write_finish_entry(self.archive._a)
  251. # Call archive.close() with _defer True to let it know we have been
  252. # closed and it is now safe to actually close.
  253. self.archive.close(_defer=True)
  254. self.archive = None
  255. self.closed = True
  256. class Entry(object):
  257. '''An entry within an archive. Represents the header data and it's location within the archive.'''
  258. def __init__(self, pathname=None, size=None, mtime=None, mode=None, hpos=None, encoding=ENCODING):
  259. # , symlink=None
  260. self.pathname = pathname
  261. self.size = size
  262. self.mtime = mtime
  263. self.mode = mode
  264. self.hpos = hpos
  265. self.encoding = encoding
  266. self.symlink = ""
  267. # if self.issym() and symlink:
  268. # self.symlink = symlink
  269. # else:
  270. # self.symlink = None
  271. @property
  272. def header_position(self):
  273. return self.hpos
  274. @classmethod
  275. def from_archive(cls, archive, encoding=ENCODING):
  276. '''Instantiates an Entry class and sets all the properties from an archive header.'''
  277. e = _libarchive.archive_entry_new()
  278. try:
  279. call_and_check(_libarchive.archive_read_next_header2, archive._a, archive._a, e)
  280. mode = _libarchive.archive_entry_filetype(e)
  281. mode |= _libarchive.archive_entry_perm(e)
  282. if PY3:
  283. pathname = _libarchive.archive_entry_pathname(e)
  284. else:
  285. pathname = _libarchive.archive_entry_pathname(e).decode(encoding)
  286. entry = cls(
  287. pathname=pathname,
  288. size=_libarchive.archive_entry_size(e),
  289. mtime=_libarchive.archive_entry_mtime(e),
  290. mode=mode,
  291. hpos=archive.header_position,
  292. )
  293. if entry.issym():
  294. print("issym")
  295. symLinkPath = _libarchive.archive_entry_symlink(e)
  296. entry.symlink = symLinkPath
  297. print(symLinkPath)
  298. finally:
  299. _libarchive.archive_entry_free(e)
  300. return entry
  301. @classmethod
  302. def from_file(cls, f, entry=None, encoding=ENCODING):
  303. '''Instantiates an Entry class and sets all the properties from a file on the file system.
  304. f can be a file-like object or a path.'''
  305. if entry is None:
  306. entry = cls(encoding=encoding)
  307. if entry.pathname is None:
  308. if isinstance(f, str):
  309. st = os.stat(f)
  310. entry.pathname = f
  311. entry.size = st.st_size
  312. entry.mtime = st.st_mtime
  313. entry.mode = st.st_mode
  314. elif hasattr(f, 'fileno'):
  315. st = os.fstat(f.fileno())
  316. entry.pathname = getattr(f, 'name', None)
  317. entry.size = st.st_size
  318. entry.mtime = st.st_mtime
  319. entry.mode = st.st_mode
  320. else:
  321. entry.pathname = getattr(f, 'pathname', None)
  322. entry.size = getattr(f, 'size', 0)
  323. entry.mtime = getattr(f, 'mtime', time.time())
  324. entry.mode = stat.S_IFREG
  325. if stat.S_ISLNK(entry.mode):
  326. print("yo")
  327. return entry
  328. def to_archive(self, archive):
  329. '''Creates an archive header and writes it to the given archive.'''
  330. e = _libarchive.archive_entry_new()
  331. try:
  332. if PY3:
  333. _libarchive.archive_entry_set_pathname(e, self.pathname)
  334. else:
  335. _libarchive.archive_entry_set_pathname(e, self.pathname.encode(self.encoding))
  336. _libarchive.archive_entry_set_filetype(e, stat.S_IFMT(self.mode))
  337. _libarchive.archive_entry_set_perm(e, stat.S_IMODE(self.mode))
  338. _libarchive.archive_entry_set_size(e, self.size)
  339. _libarchive.archive_entry_set_mtime(e, self.mtime, 0)
  340. if stat.S_ISLNK(self.mode):
  341. _libarchive.archive_entry_set_symlink(e, self.symlink)
  342. call_and_check(_libarchive.archive_write_header, archive._a, archive._a, e)
  343. # todo
  344. # self.hpos = archive.header_position
  345. finally:
  346. _libarchive.archive_entry_free(e)
  347. def isdir(self):
  348. return stat.S_ISDIR(self.mode)
  349. def isfile(self):
  350. return stat.S_ISREG(self.mode)
  351. def issym(self):
  352. return stat.S_ISLNK(self.mode)
  353. def isfifo(self):
  354. return stat.S_ISFIFO(self.mode)
  355. def ischr(self):
  356. return stat.S_ISCHR(self.mode)
  357. def isblk(self):
  358. return stat.S_ISBLK(self.mode)
  359. class Archive(object):
  360. '''A low-level archive reader which provides forward-only iteration. Consider
  361. this a light-weight pythonic libarchive wrapper.'''
  362. def __init__(
  363. self,
  364. f,
  365. mode='r',
  366. format=None,
  367. filter=None,
  368. entry_class=Entry,
  369. encoding=ENCODING,
  370. blocksize=BLOCK_SIZE,
  371. password=None,
  372. ):
  373. assert mode in ('r', 'w', 'wb', 'a'), 'Mode should be "r", "w", "wb", or "a".'
  374. self._stream = None
  375. self.encoding = encoding
  376. self.blocksize = blocksize
  377. self.password = password
  378. if isinstance(f, str):
  379. self.filename = f
  380. f = open(f, mode)
  381. # Only close it if we opened it...
  382. self._defer_close = True
  383. elif hasattr(f, 'fileno'):
  384. self.filename = getattr(f, 'name', None)
  385. # Leave the fd alone, caller should manage it...
  386. self._defer_close = False
  387. else:
  388. raise Exception('Provided file is not path or open file.')
  389. self.f = f
  390. self.mode = mode
  391. # Guess the format/filter from file name (if not provided)
  392. if self.filename:
  393. if format is None:
  394. format = guess_format(self.filename)[0]
  395. if filter is None:
  396. filter = guess_format(self.filename)[1]
  397. self.format = format
  398. self.filter = filter
  399. # The class to use for entries.
  400. self.entry_class = entry_class
  401. # Select filter/format functions.
  402. if self.mode == 'r':
  403. self.format_func = get_func(self.format, FORMATS, 0)
  404. if self.format_func is None:
  405. raise Exception('Unsupported format %s' % format)
  406. self.filter_func = get_func(self.filter, FILTERS, 0)
  407. if self.filter_func is None:
  408. raise Exception('Unsupported filter %s' % filter)
  409. else:
  410. # TODO: how to support appending?
  411. if self.format is None:
  412. raise Exception('You must specify a format for writing.')
  413. self.format_func = get_func(self.format, FORMATS, 1)
  414. if self.format_func is None:
  415. raise Exception('Unsupported format %s' % format)
  416. self.filter_func = get_func(self.filter, FILTERS, 1)
  417. if self.filter_func is None:
  418. raise Exception('Unsupported filter %s' % filter)
  419. # Open the archive, apply filter/format functions.
  420. self.init()
  421. def __iter__(self):
  422. while True:
  423. try:
  424. yield self.entry_class.from_archive(self, encoding=self.encoding)
  425. except EOF:
  426. break
  427. def __enter__(self):
  428. return self
  429. def __exit__(self, type, value, traceback):
  430. self.denit()
  431. def __del__(self):
  432. self.close()
  433. def init(self):
  434. if self.mode == 'r':
  435. self._a = _libarchive.archive_read_new()
  436. else:
  437. self._a = _libarchive.archive_write_new()
  438. self.format_func(self._a)
  439. self.filter_func(self._a)
  440. if self.mode == 'r':
  441. if self.password:
  442. self.add_passphrase(self.password)
  443. call_and_check(_libarchive.archive_read_open_fd, self._a, self._a, self.f.fileno(), self.blocksize)
  444. else:
  445. if self.password:
  446. self.set_passphrase(self.password)
  447. call_and_check(_libarchive.archive_write_open_fd, self._a, self._a, self.f.fileno())
  448. def denit(self):
  449. '''Closes and deallocates the archive reader/writer.'''
  450. if getattr(self, '_a', None) is None:
  451. return
  452. try:
  453. if self.mode == 'r':
  454. _libarchive.archive_read_close(self._a)
  455. _libarchive.archive_read_free(self._a)
  456. elif self.mode == 'w':
  457. _libarchive.archive_write_close(self._a)
  458. _libarchive.archive_write_free(self._a)
  459. finally:
  460. # We only want one try at this...
  461. self._a = None
  462. def close(self, _defer=False):
  463. # _defer == True is how a stream can notify Archive that the stream is
  464. # now closed. Calling it directly in not recommended.
  465. if _defer:
  466. # This call came from our open stream.
  467. self._stream = None
  468. if not self._defer_close:
  469. # We are not yet ready to close.
  470. return
  471. if self._stream is not None:
  472. # We have a stream open! don't close, but remember we were asked to.
  473. self._defer_close = True
  474. return
  475. self.denit()
  476. # If there is a file attached...
  477. if hasattr(self, 'f'):
  478. # Make sure it is not already closed...
  479. if getattr(self.f, 'closed', False):
  480. return
  481. # Flush it if not read-only...
  482. if hasattr(self.f, "mode") and self.f.mode != 'r' and self.f.mode != 'rb':
  483. if hasattr(self.f, "flush"):
  484. self.f.flush()
  485. if hasattr(self.f, "fileno"):
  486. os.fsync(self.f.fileno())
  487. # and then close it, if we opened it...
  488. if getattr(self, '_close', None):
  489. self.f.close()
  490. @property
  491. def header_position(self):
  492. '''The position within the file.'''
  493. return _libarchive.archive_read_header_position(self._a)
  494. def iterpaths(self):
  495. for entry in self:
  496. yield entry.pathname
  497. def read(self, size):
  498. '''Read current archive entry contents into string.'''
  499. return _libarchive.archive_read_data_into_str(self._a, size)
  500. def readpath(self, f):
  501. '''Write current archive entry contents to file. f can be a file-like object or
  502. a path.'''
  503. if isinstance(f, str):
  504. basedir = os.path.dirname(f)
  505. if not os.path.exists(basedir):
  506. os.makedirs(basedir)
  507. f = open(f, 'w')
  508. return _libarchive.archive_read_data_into_fd(self._a, f.fileno())
  509. def readstream(self, size):
  510. '''Returns a file-like object for reading current archive entry contents.'''
  511. self._stream = EntryReadStream(self, size)
  512. return self._stream
  513. def write(self, member, data=None):
  514. '''Writes a string buffer to the archive as the given entry.'''
  515. if isinstance(member, str):
  516. member = self.entry_class(pathname=member, encoding=self.encoding)
  517. if data:
  518. member.size = len(data)
  519. member.to_archive(self)
  520. if data:
  521. if PY3:
  522. if isinstance(data, bytes):
  523. result = _libarchive.archive_write_data_from_str(self._a, data)
  524. else:
  525. result = _libarchive.archive_write_data_from_str(self._a, data.encode('utf8'))
  526. else:
  527. result = _libarchive.archive_write_data_from_str(self._a, data)
  528. _libarchive.archive_write_finish_entry(self._a)
  529. def writepath(self, f, pathname=None, folder=False):
  530. '''Writes a file to the archive. f can be a file-like object or a path. Uses
  531. write() to do the actual writing.'''
  532. member = self.entry_class.from_file(f, encoding=self.encoding)
  533. if isinstance(f, str):
  534. if os.path.isfile(f):
  535. f = open(f, 'r')
  536. if pathname:
  537. member.pathname = pathname
  538. if folder and not member.isdir():
  539. member.mode = stat.S_IFDIR
  540. if hasattr(f, 'read'):
  541. # TODO: optimize this to write directly from f to archive.
  542. self.write(member, data=f.read())
  543. else:
  544. self.write(member)
  545. def writestream(self, pathname, size=None):
  546. '''Returns a file-like object for writing a new entry.'''
  547. self._stream = EntryWriteStream(self, pathname, size)
  548. return self._stream
  549. def printlist(self, s=sys.stdout):
  550. for entry in self:
  551. s.write(entry.size)
  552. s.write('\t')
  553. s.write(entry.mtime.strftime(MTIME_FORMAT))
  554. s.write('\t')
  555. s.write(entry.pathname)
  556. s.flush()
  557. def add_passphrase(self, password):
  558. '''Adds a password to the archive.'''
  559. _libarchive.archive_read_add_passphrase(self._a, password)
  560. def set_passphrase(self, password):
  561. '''Sets a password for the archive.'''
  562. _libarchive.archive_write_set_passphrase(self._a, password)
  563. class SeekableArchive(Archive):
  564. '''A class that provides random-access to archive entries. It does this by using one
  565. or many Archive instances to seek to the correct location. The best performance will
  566. occur when reading archive entries in the order in which they appear in the archive.
  567. Reading out of order will cause the archive to be closed and opened each time a
  568. reverse seek is needed.'''
  569. def __init__(self, f, **kwargs):
  570. self._stream = None
  571. # Convert file to open file. We need this to reopen the archive.
  572. mode = kwargs.setdefault('mode', 'r')
  573. if isinstance(f, str):
  574. f = open(f, mode)
  575. super(SeekableArchive, self).__init__(f, **kwargs)
  576. self.entries = []
  577. self.eof = False
  578. def __iter__(self):
  579. for entry in self.entries:
  580. yield entry
  581. if not self.eof:
  582. try:
  583. for entry in super(SeekableArchive, self).__iter__():
  584. self.entries.append(entry)
  585. yield entry
  586. except StopIteration:
  587. self.eof = True
  588. def reopen(self):
  589. '''Seeks the underlying fd to 0 position, then opens the archive. If the archive
  590. is already open, this will effectively re-open it (rewind to the beginning).'''
  591. self.denit()
  592. self.f.seek(0)
  593. self.init()
  594. def getentry(self, pathname):
  595. '''Take a name or entry object and returns an entry object.'''
  596. for entry in self:
  597. if entry.pathname == pathname:
  598. return entry
  599. raise KeyError(pathname)
  600. def seek(self, entry):
  601. '''Seeks the archive to the requested entry. Will reopen if necessary.'''
  602. move = entry.header_position - self.header_position
  603. if move != 0:
  604. if move < 0:
  605. # can't move back, re-open archive:
  606. self.reopen()
  607. # move to proper position in stream
  608. for curr in super(SeekableArchive, self).__iter__():
  609. if curr.header_position == entry.header_position:
  610. break
  611. def read(self, member):
  612. '''Return the requested archive entry contents as a string.'''
  613. entry = self.getentry(member)
  614. self.seek(entry)
  615. return super(SeekableArchive, self).read(entry.size)
  616. def readpath(self, member, f):
  617. entry = self.getentry(member)
  618. self.seek(entry)
  619. return super(SeekableArchive, self).readpath(f)
  620. def readstream(self, member):
  621. '''Returns a file-like object for reading requested archive entry contents.'''
  622. entry = self.getentry(member)
  623. self.seek(entry)
  624. self._stream = EntryReadStream(self, entry.size)
  625. return self._stream