| @@ -1,9 +1,18 @@ | |||||
| """ | """ | ||||
| Contains classes and utilities that help publishing a hyde website to | Contains classes and utilities that help publishing a hyde website to | ||||
| a filesystem using PyFilesystem FS objects. | a filesystem using PyFilesystem FS objects. | ||||
| This publisher provides an easy way to publish to FTP, SFTP, WebDAV or other | |||||
| servers by specifying a PyFS filesystem URL. For example, the following | |||||
| are valid URLs that can be used with this publisher: | |||||
| ftp://my.server.com/~username/my_blog/ | |||||
| dav:https://username:password@my.server.com/path/to/my/site | |||||
| """ | """ | ||||
| import getpass | import getpass | ||||
| import hashlib | |||||
| from hyde.fs import File, Folder | from hyde.fs import File, Folder | ||||
| from hyde.publisher import Publisher | from hyde.publisher import Publisher | ||||
| @@ -23,6 +32,10 @@ class PyFS(Publisher): | |||||
| def initialize(self, settings): | def initialize(self, settings): | ||||
| self.settings = settings | self.settings = settings | ||||
| self.url = settings.url | self.url = settings.url | ||||
| self.check_mtime = getattr(settings,"check_mtime",False) | |||||
| self.check_etag = getattr(settings,"check_etag",False) | |||||
| if self.check_etag and not isinstance(self.check_etag,basestring): | |||||
| raise ValueError("check_etag must name the etag algorithm") | |||||
| self.prompt_for_credentials() | self.prompt_for_credentials() | ||||
| self.fs = fsopendir(self.url) | self.fs = fsopendir(self.url) | ||||
| @@ -39,17 +52,48 @@ class PyFS(Publisher): | |||||
| def publish(self): | def publish(self): | ||||
| super(PyFS, self).publish() | super(PyFS, self).publish() | ||||
| deploy_fs = OSFS(self.site.config.deploy_root_path.path) | deploy_fs = OSFS(self.site.config.deploy_root_path.path) | ||||
| for (dirnm,filenms) in deploy_fs.walk(): | |||||
| for (dirnm,local_filenms) in deploy_fs.walk(): | |||||
| logger.info("Making directory: %s",dirnm) | logger.info("Making directory: %s",dirnm) | ||||
| self.fs.makedir(dirnm,allow_recreate=True) | self.fs.makedir(dirnm,allow_recreate=True) | ||||
| for filenm in filenms: | |||||
| remote_fileinfos = self.fs.listdirinfo(dirnm,files_only=True) | |||||
| # Process each local file, to see if it needs updating. | |||||
| for filenm in local_filenms: | |||||
| filepath = pathjoin(dirnm,filenm) | filepath = pathjoin(dirnm,filenm) | ||||
| # Try to find an existing remote file, to compare metadata. | |||||
| for (nm,info) in remote_fileinfos: | |||||
| if nm == filenm: | |||||
| break | |||||
| else: | |||||
| info = {} | |||||
| # Skip it if the etags match | |||||
| if self.check_etag and "etag" in info: | |||||
| with deploy_fs.open(filepath,"rb") as f: | |||||
| local_etag = self._calculate_etag(f) | |||||
| if info["etag"] == local_etag: | |||||
| logger.info("Skipping file [etag]: %s",filepath) | |||||
| continue | |||||
| # Skip it if the mtime is more recent remotely. | |||||
| if self.check_mtime and "modified_time" in info: | |||||
| local_mtime = deploy_fs.getinfo(filepath)["modified_time"] | |||||
| if info["modified_time"] > local_mtime: | |||||
| logger.info("Skipping file [mtime]: %s",filepath) | |||||
| continue | |||||
| # Upload it to the remote filesystem. | |||||
| logger.info("Uploading file: %s",filepath) | logger.info("Uploading file: %s",filepath) | ||||
| with deploy_fs.open(filepath,"rb") as f: | with deploy_fs.open(filepath,"rb") as f: | ||||
| self.fs.setcontents(filepath,f) | self.fs.setcontents(filepath,f) | ||||
| for filenm in self.fs.listdir(dirnm,files_only=True): | |||||
| # Process each remote file, to see if it needs deleting. | |||||
| for (filenm,info) in remote_fileinfos: | |||||
| filepath = pathjoin(dirnm,filenm) | filepath = pathjoin(dirnm,filenm) | ||||
| if filenm not in filenms: | |||||
| if filenm not in local_filenms: | |||||
| logger.info("Removing file: %s",filepath) | logger.info("Removing file: %s",filepath) | ||||
| self.fs.remove(filepath) | self.fs.remove(filepath) | ||||
| def _calculate_etag(self,f): | |||||
| hasher = getattr(hashlib,self.check_etag.lower())() | |||||
| data = f.read(1024*64) | |||||
| while data: | |||||
| hasher.update(data) | |||||
| data = f.read(1024*64) | |||||
| return hasher.hexdigest() | |||||