/hyde/ext/publishers/pyfs.py

http://github.com/hyde/hyde · Python · 104 lines · 73 code · 13 blank · 18 comment · 23 complexity · 79dfa480c12cd710ada972e6d9b4facd MD5 · raw file

  1. """
  2. Contains classes and utilities that help publishing a hyde website to
  3. a filesystem using PyFilesystem FS objects.
  4. This publisher provides an easy way to publish to FTP, SFTP, WebDAV or other
  5. servers by specifying a PyFS filesystem URL. For example, the following
  6. are valid URLs that can be used with this publisher:
  7. ftp://my.server.com/~username/my_blog/
  8. dav:https://username:password@my.server.com/path/to/my/site
  9. """
  10. import getpass
  11. import hashlib
  12. from hyde._compat import basestring, input
  13. from hyde.publisher import Publisher
  14. from commando.util import getLoggerWithNullHandler
  15. logger = getLoggerWithNullHandler('hyde.ext.publishers.pyfs')
  16. try:
  17. from fs.osfs import OSFS
  18. from fs.path import pathjoin
  19. from fs.opener import fsopendir
  20. except ImportError:
  21. logger.error("The PyFS publisher requires PyFilesystem v0.4 or later.")
  22. logger.error("`pip install -U fs` to get it.")
  23. raise
  24. class PyFS(Publisher):
  25. def initialize(self, settings):
  26. self.settings = settings
  27. self.url = settings.url
  28. self.check_mtime = getattr(settings, "check_mtime", False)
  29. self.check_etag = getattr(settings, "check_etag", False)
  30. if self.check_etag and not isinstance(self.check_etag, basestring):
  31. raise ValueError("check_etag must name the etag algorithm")
  32. self.prompt_for_credentials()
  33. self.fs = fsopendir(self.url)
  34. def prompt_for_credentials(self):
  35. credentials = {}
  36. if "%(username)s" in self.url:
  37. print("Username: ",)
  38. credentials["username"] = input().strip()
  39. if "%(password)s" in self.url:
  40. credentials["password"] = getpass.getpass("Password: ")
  41. if credentials:
  42. self.url = self.url % credentials
  43. def publish(self):
  44. super(PyFS, self).publish()
  45. deploy_fs = OSFS(self.site.config.deploy_root_path.path)
  46. for (dirnm, local_filenms) in deploy_fs.walk():
  47. logger.info("Making directory: %s", dirnm)
  48. self.fs.makedir(dirnm, allow_recreate=True)
  49. remote_fileinfos = self.fs.listdirinfo(dirnm, files_only=True)
  50. # Process each local file, to see if it needs updating.
  51. for filenm in local_filenms:
  52. filepath = pathjoin(dirnm, filenm)
  53. # Try to find an existing remote file, to compare metadata.
  54. for (nm, info) in remote_fileinfos:
  55. if nm == filenm:
  56. break
  57. else:
  58. info = {}
  59. # Skip it if the etags match
  60. if self.check_etag and "etag" in info:
  61. with deploy_fs.open(filepath, "rb") as f:
  62. local_etag = self._calculate_etag(f)
  63. if info["etag"] == local_etag:
  64. logger.info("Skipping file [etag]: %s", filepath)
  65. continue
  66. # Skip it if the mtime is more recent remotely.
  67. if self.check_mtime and "modified_time" in info:
  68. local_mtime = deploy_fs.getinfo(filepath)["modified_time"]
  69. if info["modified_time"] > local_mtime:
  70. logger.info("Skipping file [mtime]: %s", filepath)
  71. continue
  72. # Upload it to the remote filesystem.
  73. logger.info("Uploading file: %s", filepath)
  74. with deploy_fs.open(filepath, "rb") as f:
  75. self.fs.setcontents(filepath, f)
  76. # Process each remote file, to see if it needs deleting.
  77. for (filenm, info) in remote_fileinfos:
  78. filepath = pathjoin(dirnm, filenm)
  79. if filenm not in local_filenms:
  80. logger.info("Removing file: %s", filepath)
  81. self.fs.remove(filepath)
  82. def _calculate_etag(self, f):
  83. hasher = getattr(hashlib, self.check_etag.lower())()
  84. data = f.read(1024 * 64)
  85. while data:
  86. hasher.update(data)
  87. data = f.read(1024 * 64)
  88. return hasher.hexdigest()