PageRenderTime 30ms CodeModel.GetById 1ms app.highlight 22ms RepoModel.GetById 2ms app.codeStats 0ms

/hyde/ext/publishers/pyfs.py

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