""" """ import os import time import dropbox.exceptions from dropbox.paper import ExportFormat from git import Repo, GitCommandError from papergit.errors import NoDestinationError, DocDoesNotExist from papergit.utilities.dropbox import dropbox_api from papergit.utilities.modules import create_file_name from papergit.utilities.general import generate_metadata from papergit.config import config from peewee import ( Model, CharField, ForeignKeyField, IntegerField, TimestampField, PrimaryKeyField) __all__ = [ 'PaperDoc', 'PaperFolder', 'Sync', ] class BasePaperModel(Model): """This is base model from Dropbox Paper. All the paper documents be it folder or document subclass this. It provides some very basic functionalities. """ class Meta: database = config.db.db class PaperFolder(BasePaperModel): """Representation of a Dropbox Paper folder""" name = CharField() folder_id = CharField() def __repr__(self): return "Folder {}".format(self.name) class PaperDoc(BasePaperModel): """Representation of a Dropbox Paper document.""" title = CharField() paper_id = CharField() version = IntegerField(default=0) folder = ForeignKeyField(PaperFolder, null=True, related_name='docs') last_updated = TimestampField() last_published = TimestampField(null=True) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def __repr__(self): return "{}: Document {} at version {}".format( self.id, self.title, self.version) @classmethod def get_by_paper_id(self, paper_id): return PaperDoc.get(PaperDoc.paper_id == paper_id) def get_changes(self): """Update this record with the latest version of the document. Also, download the latest version to the file. """ title, rev = PaperDoc.download_doc(self.paper_id) if rev > self.version: print('Update revision for doc {0} from {1} to {2}'.format( self.title, self.version, rev)) self.version = rev self.last_updated = time.time() if self.title != title: self.title = title self.last_updated = time.time() self.save() self.update_folder_info() @classmethod def generate_file_path(self, doc_id): return os.path.join(config.CACHE_DIR, doc_id + '.md') @classmethod @dropbox_api def sync_docs(self, dbx): """Fetches all the doc ids from the given dropbox handler. Args: dbx(dropbox.Dropbox): an instance of initialized dropbox handler Returns: An array of all the doc ids. """ docs = dbx.paper_docs_list() for doc_id in docs.doc_ids: try: doc = PaperDoc.get(PaperDoc.paper_id == doc_id) if not os.path.exists(self.generate_file_path(doc_id)): self.download_doc(doc_id) except PaperDoc.DoesNotExist: title, rev = self.download_doc(doc_id) doc = PaperDoc.create(paper_id=doc_id, title=title, version=rev, last_updated=time.time()) doc.update_folder_info() print(doc) @classmethod @dropbox_api def download_doc(self, dbx, doc_id): """Downloads the given doc_id to the local file cache. """ path = self.generate_file_path(doc_id) result = dbx.paper_docs_download_to_file( path, doc_id, ExportFormat.markdown) return (result.title, result.revision) @dropbox_api def update_folder_info(self, dbx): """Fetch and update the folder information for the current PaperDoc. """ folders = dbx.paper_docs_get_folder_info(self.paper_id) if folders.folders is None: return folder = folders.folders[0] f = PaperFolder.get_or_create(folder_id=folder.id, name=folder.name)[0] self.folder = f self.save() def publish(self, push=False): """Publish the document as a blog post. Process: - Find if this document belongs to a PaperFolder, - If yes, find if that PaperFolder is a part of a Sync, - If yes, find if there already exists a file at the destination, - If no, create the file, copy it to destination - If yes, still copy the file to the destination (Later it will fail and allow to view a diff of the changes that will be made to the destination file.) """ if self.folder: try: sync = Sync.get(folder=self.folder) sync.sync_single(doc=self, commit=True, push=push) self.last_published = time.time() self.save() except Sync.DoesNotExist: raise NoDestinationError except DocDoesNotExist: self.download_doc() self.publish(push=push) return True raise NoDestinationError @property def sync_path(self): """Returns the destination path if the sync were to run on doc. It needs this doc to belong to a PaperFolder and that PaperFolder to be a part of a Sync. Otherwise, it returns None Returns (document's path, destination path) """ try: sync = Sync.get(folder=self.folder) return sync.get_doc_sync_path(self) except Sync.DoesNotExist: return None except DocDoesNotExist: self.download_doc() return self.sync_path class Sync(BasePaperModel): """Representation of a synchronization between a Git repo and a PaperFolder. Files are synchronized only after a few changes are made and the metadata is added. Files with #draft in them is not synchronized to the git repo. """ # Path to the Git Repo. repo = CharField() # Path to the directories in the git repo. path_in_repo = CharField() folder = ForeignKeyField(PaperFolder) last_run = TimestampField(null=True) def __repr__(self): return "Folder {} to Git repo at {} at path {}".format( self.folder.name, self.repo, self.path_in_repo) def sync(self, commit=True, push=False): for doc in self.folder.docs: self.sync_single(doc, commit=False, push=False) if commit: self.commit_changes(push=push) def sync_single(self, doc, commit=True, push=False): original_path, final_path = self.get_doc_sync_path(doc) with open(final_path, 'w+') as fp: print(generate_metadata(doc=doc), file=fp) with open(original_path, 'r') as op: print(op.read(), file=fp) if commit: self.commit_changes(push=push) def get_doc_sync_path(self, doc): original_path = PaperDoc.generate_file_path(doc.paper_id) file_name = create_file_name(doc.title) final_path = os.path.join(self.repo, self.path_in_repo, file_name) return (original_path, final_path) def commit_changes(self, push=False): git_repo = Repo(self.repo) git_repo.git.add('content/') try: git_repo.git.commit('-m', 'Commit added by PaperGit') self.last_run = time.time() self.save() except GitCommandError: print('Nothing to commit') if push: print("Pushing changes to remote") git_repo.git.push('origin')