def merge(self, remote_file, remote_content, automerge_rules, amerge_ref_is_local): # Fetch local content for this file local_file, local_content = self.get_local_content(remote_file) # Merge if remote_content is None: if local_content is None: merged_content = None else: merged_content = local_content elif local_content and local_content != remote_content: # If automerge selected try one or more strategies merge_strategy = None if automerge_rules: # Select reference. Automerge will try to keep the information # in it according to the rules. If it fails manual merge will # fire. if amerge_ref_is_local: merged_content, merge_strategy = automerge( local_content, remote_content, automerge_rules, ) else: merged_content, merge_strategy = automerge( remote_content, local_content, automerge_rules, ) if merge_strategy is None: old_local_file = "%s.local" % local_file local.write_file(old_local_file, local_content) local.write_file(local_file, remote_content) local.mergetool(old_local_file, local_file) merged_content = local.read_file(local_file) # Clean up extra temporary file os.remove(old_local_file) else: local.write_file(local_file, merged_content) if self.verbose > 0: print("merged by %s" % merge_strategy) else: # No local content, or local matches remote merged_content = remote_content local.local_edit(local_file, merged_content, no_edit=True) return { 'local': local_content, 'remote': remote_content, 'merged': merged_content }
def manual_edit(local_file, content): """Write empty file with given text""" if content['remote']: if content['remote'] == content['merged']: return content['merged'] elif content['remote'] != content['merged']: raise Exception( "File exists in remote and differs in content") else: return local.local_edit(local_file, initial_text, no_edit=True)
def edit_paper_url(url): from vimbox.remote.paper_backend import StorageBackEnd, DOC_REGEX title, did, doc_id = DOC_REGEX.match(url).groups() paper_token = local.load_config()['DROPBOX_TOKEN'] client = StorageBackEnd(paper_token) response = client.file_download(url) local_folder = local.get_local_file('.paper/') if not os.path.isdir(local_folder): os.mkdir(local_folder) local_file = "%s/%s--%s-%s.md" % (local_folder, title, did, doc_id) content = local.local_edit(local_file, response['content']) # Update remote if there are changes if content != response['content']: response = client.files_upload(content, url, response['revision']) if response['status'] == 'api-error': print(response['alert']) os.remove(local_file)
def update_rules(self, remote_file, content, password, status, register_folder, remove_local): # Update remote if content['edited'] != content['remote']: # Try to push into remote if status != 'connection-error': error = self._push(content['edited'], remote_file, password=password) else: error = 'connection-error' # Inform the user if error is None: # We pushed sucessfully if self.verbose > 0: print("%-12s %s" % (yellow("pushed"), remote_file)) elif error == 'connection-error': # Offline if self.verbose > 0: print("%-12s %s" % (red("offline"), remote_file)) elif error == 'api-error': # This is not a normal state. Probably bug on our side or API # change/bug on the backend. if self.verbose > 0: print("%-12s %s" % (red("api-error"), remote_file)) print("API error (something bad happened)") else: # This is most certainly a bug on our side raise Exception("Unknown _push error %s" % error) # Update local after updating remote # Register file in cache if register_folder and error != 'api-error': self.register_file(remote_file, password is not None) # Remove local copy if solicited, otherwise update it with new # content local_file = self.get_local_file(remote_file) if remove_local and os.path.isfile(local_file): if (content['merged'] != content['remote'] and error is not None): # If we could not update the remote but we are forced to # remove the file we must prompt user first _ = input( "We are offline but I have to remove local files." " Will open the file once more for you to save " "stuff, then it will be nuked. (press any key " "when ready)") local.local_edit(local_file, content['merged']) # Remove local file if solicited os.remove(local_file) if self.verbose > 0: print("%-12s %s" % (red("cleaned"), local_file)) elif content['local'] != content['merged']: # If local content was changed we need to update local.local_edit(local_file, content['merged'], no_edit=True) elif content['merged'] != content['local'] and not remove_local: # We overwrote local with remote local_file = self.get_local_file(remote_file) local.local_edit(local_file, content['merged'], no_edit=True) if self.verbose > 0: print("%-12s %s" % (yellow("pulled"), remote_file)) if register_folder: self.register_file(remote_file, password is not None) elif all(content[key] is None for key in ['merged', 'local', 'remote', 'edited']): # Edit aborted pass else: # No changes needed on either side if self.verbose > 0: print("%-12s %s" % (green("in-sync"), remote_file)) if remove_local and os.path.isfile(local_file): os.remove(local_file) if self.verbose > 0: print("%-12s %s" % (red("cleaned"), local_file)) if register_folder: self.register_file(remote_file, password is not None)
def sync(self, remote_file, remove_local=None, force_creation=False, register_folder=True, password=None, automerge_rules=None, amerge_ref_is_local=False, edits=None): """ Syncronize remote and local content with optional edit Edits will happen on a local copy that will be uploded when finished. remove_local After sucesful push remove local content force_creation Mandatory for new file on remote register_folder Store file path in local cache password Optional encryption/decription on client side automerge_rules Allowed way to automerge amerge_ref_is_local If valid automerge use local as reference (default is remote) """ # Sanity checks if remote_file[-1] == '/': raise VimboxClientError("Can not edit folders") if remove_local is None: remove_local = self.config['remove_local'] # Fetch remote content, merge if neccesary with local.mergetool # will provide local, remote and merged copies content, fetch_status, password = self.pull( remote_file, force_creation, password=password, automerge_rules=automerge_rules, amerge_ref_is_local=amerge_ref_is_local) # Apply edit if needed local_file = self.get_local_file(remote_file) dirname = os.path.dirname(local_file) if not os.path.isdir(dirname): os.mkdir(dirname) if edits: content['edited'] = edits(local_file, content) else: # No edits (still need to be sure we update local) content['edited'] = local.local_edit(local_file, content['merged'], no_edit=True) # Update local and remote # Abort if file being created but no changes if (content['edited'] is None and content['remote'] is not None and fetch_status != 'connection-error'): # For debug purposes VimboxClientError( "\nInvalid state: edited local_file non existing but remote" " does\n") # Pull again if recovered offline status if fetch_status == 'connection-error': content2, fetch_status, password = self.pull( remote_file, force_creation, password=password, automerge_rules=automerge_rules, amerge_ref_is_local=amerge_ref_is_local) if fetch_status != 'connection-error': content = content2 content['edited'] = content['remote'] self.update_rules(remote_file, content, password, fetch_status, register_folder, remove_local) return content['local'] == content['remote']
def manual_edit(local_file, content): """Prompt user for edit""" return local.local_edit(local_file, content['merged'])