FilePackPlugin.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. import os
  2. import re
  3. import gevent
  4. from Plugin import PluginManager
  5. from Config import config
  6. from Debug import Debug
  7. # Keep archive open for faster reponse times for large sites
  8. archive_cache = {}
  9. def closeArchive(archive_path):
  10. if archive_path in archive_cache:
  11. del archive_cache[archive_path]
  12. def openArchive(archive_path, file_obj=None):
  13. if archive_path not in archive_cache:
  14. if archive_path.endswith("tar.gz"):
  15. import tarfile
  16. archive_cache[archive_path] = tarfile.open(archive_path, fileobj=file_obj, mode="r:gz")
  17. else:
  18. import zipfile
  19. archive_cache[archive_path] = zipfile.ZipFile(file_obj or archive_path)
  20. gevent.spawn_later(5, lambda: closeArchive(archive_path)) # Close after 5 sec
  21. archive = archive_cache[archive_path]
  22. return archive
  23. def openArchiveFile(archive_path, path_within, file_obj=None):
  24. archive = openArchive(archive_path, file_obj=file_obj)
  25. if archive_path.endswith(".zip"):
  26. return archive.open(path_within)
  27. else:
  28. return archive.extractfile(path_within)
  29. @PluginManager.registerTo("UiRequest")
  30. class UiRequestPlugin(object):
  31. def actionSiteMedia(self, path, **kwargs):
  32. if ".zip/" in path or ".tar.gz/" in path:
  33. file_obj = None
  34. path_parts = self.parsePath(path)
  35. file_path = "%s/%s/%s" % (config.data_dir, path_parts["address"], path_parts["inner_path"])
  36. match = re.match("^(.*\.(?:tar.gz|zip))/(.*)", file_path)
  37. archive_path, path_within = match.groups()
  38. if archive_path not in archive_cache:
  39. site = self.server.site_manager.get(path_parts["address"])
  40. if not site:
  41. return self.actionSiteAddPrompt(path)
  42. archive_inner_path = site.storage.getInnerPath(archive_path)
  43. if not os.path.isfile(archive_path):
  44. # Wait until file downloads
  45. result = site.needFile(archive_inner_path, priority=10)
  46. # Send virutal file path download finished event to remove loading screen
  47. site.updateWebsocket(file_done=archive_inner_path)
  48. if not result:
  49. return self.error404(archive_inner_path)
  50. file_obj = site.storage.openBigfile(archive_inner_path)
  51. if file_obj == False:
  52. file_obj = None
  53. header_allow_ajax = False
  54. if self.get.get("ajax_key"):
  55. requester_site = self.server.site_manager.get(path_parts["request_address"])
  56. if self.get["ajax_key"] == requester_site.settings["ajax_key"]:
  57. header_allow_ajax = True
  58. else:
  59. return self.error403("Invalid ajax_key")
  60. try:
  61. file = openArchiveFile(archive_path, path_within, file_obj=file_obj)
  62. content_type = self.getContentType(file_path)
  63. self.sendHeader(200, content_type=content_type, noscript=kwargs.get("header_noscript", False), allow_ajax=header_allow_ajax)
  64. return self.streamFile(file)
  65. except Exception as err:
  66. self.log.debug("Error opening archive file: %s" % Debug.formatException(err))
  67. return self.error404(path)
  68. return super(UiRequestPlugin, self).actionSiteMedia(path, **kwargs)
  69. def streamFile(self, file):
  70. for i in range(100): # Read max 6MB
  71. try:
  72. block = file.read(60 * 1024)
  73. if block:
  74. yield block
  75. else:
  76. raise StopIteration
  77. except StopIteration:
  78. file.close()
  79. break
  80. @PluginManager.registerTo("SiteStorage")
  81. class SiteStoragePlugin(object):
  82. def isFile(self, inner_path):
  83. if ".zip/" in inner_path or ".tar.gz/" in inner_path:
  84. match = re.match("^(.*\.(?:tar.gz|zip))/(.*)", inner_path)
  85. archive_inner_path, path_within = match.groups()
  86. return super(SiteStoragePlugin, self).isFile(archive_inner_path)
  87. else:
  88. return super(SiteStoragePlugin, self).isFile(inner_path)
  89. def openArchive(self, inner_path):
  90. archive_path = self.getPath(inner_path)
  91. file_obj = None
  92. if archive_path not in archive_cache:
  93. if not os.path.isfile(archive_path):
  94. result = self.site.needFile(inner_path, priority=10)
  95. self.site.updateWebsocket(file_done=inner_path)
  96. if not result:
  97. raise Exception("Unable to download file")
  98. file_obj = self.site.storage.openBigfile(inner_path)
  99. if file_obj == False:
  100. file_obj = None
  101. try:
  102. archive = openArchive(archive_path, file_obj=file_obj)
  103. except Exception as err:
  104. raise Exception("Unable to download file: %s" % Debug.formatException(err))
  105. return archive
  106. def walk(self, inner_path, *args, **kwags):
  107. if ".zip" in inner_path or ".tar.gz" in inner_path:
  108. match = re.match("^(.*\.(?:tar.gz|zip))(.*)", inner_path)
  109. archive_inner_path, path_within = match.groups()
  110. archive = self.openArchive(archive_inner_path)
  111. path_within = path_within.lstrip("/")
  112. if archive_inner_path.endswith(".zip"):
  113. namelist = [name for name in archive.namelist() if not name.endswith("/")]
  114. else:
  115. namelist = [item.name for item in archive.getmembers() if not item.isdir()]
  116. namelist_relative = []
  117. for name in namelist:
  118. if not name.startswith(path_within):
  119. continue
  120. name_relative = name.replace(path_within, "", 1).rstrip("/")
  121. namelist_relative.append(name_relative)
  122. return namelist_relative
  123. else:
  124. return super(SiteStoragePlugin, self).walk(inner_path, *args, **kwags)
  125. def list(self, inner_path, *args, **kwags):
  126. if ".zip" in inner_path or ".tar.gz" in inner_path:
  127. match = re.match("^(.*\.(?:tar.gz|zip))(.*)", inner_path)
  128. archive_inner_path, path_within = match.groups()
  129. archive = self.openArchive(archive_inner_path)
  130. path_within = path_within.lstrip("/")
  131. if archive_inner_path.endswith(".zip"):
  132. namelist = [name for name in archive.namelist()]
  133. else:
  134. namelist = [item.name for item in archive.getmembers()]
  135. namelist_relative = []
  136. for name in namelist:
  137. if not name.startswith(path_within):
  138. continue
  139. name_relative = name.replace(path_within, "", 1).rstrip("/")
  140. if "/" in name_relative: # File is in sub-directory
  141. continue
  142. namelist_relative.append(name_relative)
  143. return namelist_relative
  144. else:
  145. return super(SiteStoragePlugin, self).list(inner_path, *args, **kwags)
  146. def read(self, inner_path, mode="rb", **kwargs):
  147. if ".zip/" in inner_path or ".tar.gz/" in inner_path:
  148. match = re.match("^(.*\.(?:tar.gz|zip))(.*)", inner_path)
  149. archive_inner_path, path_within = match.groups()
  150. archive = self.openArchive(archive_inner_path)
  151. path_within = path_within.lstrip("/")
  152. if archive_inner_path.endswith(".zip"):
  153. return archive.open(path_within).read()
  154. else:
  155. return archive.extractfile(path_within).read()
  156. else:
  157. return super(SiteStoragePlugin, self).read(inner_path, mode, **kwargs)