nncli

NextCloud Notes Command Line Interface
git clone git://git.danielmoch.com/nncli.git
Log | Files | Refs | LICENSE

commit c5bb932afc29b9956c6b2f3dee526183fadc53fa
parent 408d09e5f9b482cef04bc263bd8730dd921ec7c4
Author: Eric Davis <edavis@insanum.com>
Date:   Tue,  8 Jul 2014 00:40:14 -0700

removed notes_db observer interface, converted to simple callback logging

Diffstat:
Mnotes_db.py | 162++++++++++++++++++++++++++++++++-----------------------------------------------
Msncli.py | 14--------------
Mutils.py | 37+------------------------------------
3 files changed, 67 insertions(+), 146 deletions(-)

diff --git a/notes_db.py b/notes_db.py @@ -2,7 +2,6 @@ # copyright 2012 by Charl P. Botha <cpbotha@vxlabs.com> # new BSD license -import codecs import copy import glob import os @@ -26,12 +25,10 @@ class ReadError(RuntimeError): class WriteError(RuntimeError): pass -class NotesDB(utils.SubjectMixin): +class NotesDB(): """NotesDB will take care of the local notes database and syncing with SN. """ def __init__(self, config, log): - utils.SubjectMixin.__init__(self) - self.config = config self.log = log @@ -334,11 +331,8 @@ def set_note_deleted(self, key): if not n['deleted']: n['deleted'] = 1 n['modifydate'] = time.time() - self.notify_observers('change:note-status', - utils.KeyValueObject(what='modifydate', - key=key, - msg='Note trashed.')) self.flag_what_changed(n, 'deleted') + self.log('Note trashed') def set_note_content(self, key, content): n = self.notes[key] @@ -346,11 +340,8 @@ def set_note_content(self, key, content): if content != old_content: n['content'] = content n['modifydate'] = time.time() - self.notify_observers('change:note-status', - utils.KeyValueObject(what='modifydate', - key=key, - msg='Note content updated.')) self.flag_what_changed(n, 'content') + self.log('Note content updated') def set_note_tags(self, key, tags): n = self.notes[key] @@ -359,11 +350,8 @@ def set_note_tags(self, key, tags): if tags != old_tags: n['tags'] = tags n['modifydate'] = time.time() - self.notify_observers('change:note-status', - utils.KeyValueObject(what='modifydate', - key=key, - msg='Note tags updated.')) self.flag_what_changed(n, 'tags') + self.log('Note tags updated') def set_note_pinned(self, key, pinned): n = self.notes[key] @@ -377,11 +365,8 @@ def set_note_pinned(self, key, pinned): else: systemtags.remove('pinned') n['modifydate'] = time.time() - self.notify_observers('change:note-status', - utils.KeyValueObject(what='modifydate', - key=key, - msg='Note pinned.' if pinned else 'Note unpinned.')) self.flag_what_changed(n, 'systemtags') + self.log('Note pinned' if pinned else 'Note unpinned') def set_note_markdown(self, key, markdown): n = self.notes[key] @@ -395,11 +380,8 @@ def set_note_markdown(self, key, markdown): else: systemtags.remove('markdown') n['modifydate'] = time.time() - self.notify_observers('change:note-status', - utils.KeyValueObject(what='modifydate', - key=key, - msg='Note markdown flagged.' if markdown else 'Note markdown unflagged.')) self.flag_what_changed(n, 'systemtags') + self.log('Note markdown flagged' if markdown else 'Note markdown unflagged') def helper_key_to_fname(self, k): return os.path.join(self.config.get_config('db_path'), k) + '.json' @@ -527,8 +509,7 @@ def sync_full(self): 3. for each remote note if remote syncnum > local syncnum || - retrieve note, update note with response - if new note, key is not in local store + a new note and key is not in local store retrieve note, update note with response 4. for each local note not in the index @@ -537,119 +518,108 @@ def sync_full(self): local_updates = {} local_deletes = {} + server_keys = {} now = time.time() - self.notify_observers('progress:sync_full', utils.KeyValueObject(msg='Starting full sync.')) + self.log('Starting full sync') - # 1. go through local notes, if anything changed or new, update to server - for ni,lk in enumerate(self.notes.keys()): - n = self.notes[lk] - if not n.get('key') or float(n.get('modifydate')) > float(n.get('syncdate')): + # 1. for any note changed locally, including new notes: + # save note to server, update note with response + for note_index, local_key in enumerate(self.notes.keys()): + n = self.notes[local_key] + if not n.get('key') or \ + float(n.get('modifydate')) > float(n.get('syncdate')): uret = self.simplenote.update_note(n) - if uret[1] == 0: - # replace n with uret[0] - # if this was a new note, our local key is not valid anymore - del self.notes[lk] - # in either case (new or existing note), save note at assigned key + if uret[1] == 0: # success + # if this is a new note our local key is not valid anymore + # merge the note we got back (content could be empty) + # record syncdate and save the note at the assigned key + del self.notes[local_key] k = uret[0].get('key') - # we merge the note we got back (content could be empty!) n.update(uret[0]) - # and put it at the new key slot + n['syncdate'] = now self.notes[k] = n - # record that we just synced - uret[0]['syncdate'] = now - # whatever the case may be, k is now updated local_updates[k] = True - if lk != k: - # if lk was a different (purely local) key, should be deleted - local_deletes[lk] = True - self.notify_observers('progress:sync_full', - utils.KeyValueObject(msg='Synced modified note %d to server. (key=%s)' % (ni,lk))) + if local_key != k: + # if local_key was a different key it should be deleted + local_deletes[local_key] = True + self.log('Synced note to server (key={0})'.format(local_key)) else: - self.notify_observers('progress:sync_full', - utils.KeyValueObject(msg='ERROR: Failed to sync modified note %d to server. (key=%s)' % (ni,lk))) - self.notify_observers('progress:sync_full', - utils.KeyValueObject(msg="SyncError: " + str(uret[0]))) + self.log('ERROR: Failed to sync note to server (key={0})'.format(local_key)) # 2. get the note index - self.notify_observers('progress:sync_full', - utils.KeyValueObject(msg='Retrieving note list from server.')) + self.log('Retrieving note list from server') nl = self.simplenote.get_note_list() if nl[1] == 0: # success - self.notify_observers('progress:sync_full', - utils.KeyValueObject(msg='Retrieved full note list from server.')) + self.log('Retrieved full note list from server') else: - self.notify_observers('progress:sync_full', - utils.KeyValueObject(msg='ERROR: Could not get note list from server.')) + self.log('ERROR: Could not get note list from server') return 1 nl = nl[0] - # 3. if remote syncnum > local syncnum, update our note; if key is new, add note to local. - # this gets the FULL note list, even if multiple gets are required - server_keys = {} - lennl = len(nl) + # 3. for each remote note + # if remote syncnum > local syncnum || + # a new note and key is not in local store + # retrieve note, update note with response + len_nl = len(nl) sync_from_server_errors = 0 - for ni,n in enumerate(nl): + for note_index, n in enumerate(nl): k = n.get('key') server_keys[k] = True - # this works, only because in phase 1 we rewrite local keys to - # server keys when we get an updated not back from the server + # this works because in the prior step we rewrite local keys to + # server keys when we get an updated note back from the server if k in self.notes: - # we already have this - # check if server n has a newer syncnum than mine + # we already have this note + # if the server note has a newer syncnum we need to get it if int(n.get('syncnum')) > int(self.notes[k].get('syncnum', -1)): - # and the server is newer - ret = self.simplenote.get_note(k) - if ret[1] == 0: - self.notes[k].update(ret[0]) + gret = self.simplenote.get_note(k) + if gret[1] == 0: + self.notes[k].update(gret[0]) local_updates[k] = True - # in both cases, new or newer note, syncdate is now. self.notes[k]['syncdate'] = now - self.notify_observers('progress:sync_full', - utils.KeyValueObject(msg='Synced newer note %d (%d) from server.' % (ni,lennl))) - else: - self.notify_observers('progress:sync_full', - utils.KeyValueObject(msg='ERROR: Failed to sync newer note %s from server: %s' % (k,ret[0]))) - sync_from_server_errors+=1 + self.log('Synced newer note from server (key={0})'.format(k)) + else: + self.log('ERROR: Failed to sync newer note from server (key={0})'.format(k)) + sync_from_server_errors += 1 else: - # new note - ret = self.simplenote.get_note(k) - if ret[1] == 0: - self.notes[k] = ret[0] + # this is a new note + gret = self.simplenote.get_note(k) + if gret[1] == 0: + self.notes[k] = gret[0] local_updates[k] = True - # in both cases, new or newer note, syncdate is now. self.notes[k]['syncdate'] = now - self.notify_observers('progress:sync_full', - utils.KeyValueObject(msg='Synced new note %d (%d) from server.' % (ni,lennl))) + + self.log('Synced new note from server (key={0})'.format(k)) else: - self.notify_observers('progress:sync_full', - utils.KeyValueObject(msg='ERROR: Failed syncing new note %s from server: %s' % (k,ret[0]))) - sync_from_server_errors+=1 + self.log('ERROR: Failed syncing new note from server (key={0})'.format(k)) + sync_from_server_errors += 1 - # 4. for each local note not in server index, remove. - for lk in self.notes.keys(): - if lk not in server_keys: - del self.notes[lk] - local_deletes[lk] = True + # 4. for each local note not in the index + # PERMANENT DELETE, remove note from local store + for local_key in self.notes.keys(): + if local_key not in server_keys: + del self.notes[local_key] + local_deletes[local_key] = True # sync done, now write changes to db_path - for uk in local_updates.keys(): + + for k in local_updates.keys(): try: - self.helper_save_note(uk, self.notes[uk]) + self.helper_save_note(k, self.notes[k]) except WriteError, e: raise WriteError (str(e)) - for dk in local_deletes.keys(): - fn = self.helper_key_to_fname(dk) + for k in local_deletes.keys(): + fn = self.helper_key_to_fname(k) if os.path.exists(fn): os.unlink(fn) - self.notify_observers('progress:sync_full', utils.KeyValueObject(msg='Full sync complete.')) + self.log('Full sync complete') return sync_from_server_errors diff --git a/sncli.py b/sncli.py @@ -46,16 +46,6 @@ def gui_sync_full_threaded(self): def gui_sync_full_initial(self, loop, arg): self.gui_sync_full_threaded() - def gui_observer_notes_db_change_note_status(self, ndb, evt_type, evt): - self.log(evt.msg) - - def gui_observer_notes_db_sync_full(self, ndb, evt_type, evt): - self.log(evt.msg) - - def gui_observer_notes_db_synced_note(self, ndb, evt_type, evt): - self.log(evt.msg) - # XXX update view if note synced back is the visible one - def get_editor(self): editor = self.config.get_config('editor') if not editor and os.environ['EDITOR']: @@ -648,10 +638,6 @@ def gui(self, do_sync): self.thread_sync = threading.Thread(target=self.ndb.sync_worker) self.thread_sync.setDaemon(True) - self.ndb.add_observer('synced:note', self.gui_observer_notes_db_synced_note) - self.ndb.add_observer('change:note-status', self.gui_observer_notes_db_change_note_status) - self.ndb.add_observer('progress:sync_full', self.gui_observer_notes_db_sync_full) - self.view_titles = \ view_titles.ViewTitles(self.config, { diff --git a/utils.py b/utils.py @@ -2,11 +2,7 @@ # copyright 2012 by Charl P. Botha <cpbotha@vxlabs.com> # new BSD license -import datetime -import random -import re -import string -import urllib2 +import datetime, random, re # first line with non-whitespace should be the title note_title_re = re.compile('\s*(.*)\n?') @@ -146,34 +142,3 @@ class KeyValueObject: def __init__(self, **kwargs): self.__dict__.update(kwargs) -class SubjectMixin: - """Maintain a list of callables for each event type. - - We follow the convention action:object, e.g. change:entry. - """ - - def __init__(self): - self.observers = {} - self.mutes = {} - - def add_observer(self, evt_type, o): - if evt_type not in self.observers: - self.observers[evt_type] = [o] - - elif o not in self.observers[evt_type]: - self.observers[evt_type].append(o) - - def notify_observers(self, evt_type, evt): - if evt_type in self.mutes or evt_type not in self.observers: - return - - for o in self.observers[evt_type]: - # invoke observers with ourselves as first param - o(self, evt_type, evt) - - def mute(self, evt_type): - self.mutes[evt_type] = True - - def unmute(self, evt_type): - if evt_type in self.mutes: - del self.mutes[evt_type]