def __init__(self, do_server_sync, verbose=False, config_file=None): self.config = Config(config_file) self.do_server_sync = do_server_sync self.verbose = verbose self.do_gui = False force_full_sync = False if not os.path.exists(self.config.get_config('db_path')): os.mkdir(self.config.get_config('db_path')) force_full_sync = True # configure the logging module self.logfile = os.path.join(self.config.get_config('db_path'), 'sncli.log') self.loghandler = RotatingFileHandler(self.logfile, maxBytes=100000, backupCount=1) self.loghandler.setLevel(logging.DEBUG) self.loghandler.setFormatter( logging.Formatter(fmt='%(asctime)s [%(levelname)s] %(message)s')) self.logger = logging.getLogger() self.logger.setLevel(logging.DEBUG) self.logger.addHandler(self.loghandler) self.config.logfile = self.logfile logging.debug('sncli logging initialized') self.logs = [] try: self.ndb = NotesDB(self.config, self.log, self.gui_update_view) except Exception, e: self.log(str(e)) sys.exit(1)
def __init__(self): # setup appdir if hasattr(sys, 'frozen') and sys.frozen: self.appdir, _ = os.path.split(sys.executable) else: dirname = os.path.dirname(__file__) if dirname and dirname != os.curdir: self.appdir = dirname else: self.appdir = os.getcwd() # make sure it's the full path self.appdir = os.path.abspath(self.appdir) # should probably also look in $HOME self.config = Config(self.appdir) self.config.app_version = self.get_version() # read our database of notes into memory # and sync with simplenote. c = self.config notes_db_config = KeyValueObject(db_path=c.db_path, sn_username=c.sn_username, sn_password=c.sn_password, sort_mode=c.sort_mode) self.notes_db = NotesDB(notes_db_config) self.notes_db.add_observer('synced:note', self.observer_notes_db_synced_note) self.notes_db.add_observer('change:note-status', self.observer_notes_db_change_note_status) self.notes_db.add_observer('progress:sync_full', self.observer_notes_db_sync_full) self.notes_list_model = NotesListModel() # create the interface self.view = view.View(self.config, self.notes_list_model) # we want to be notified when the user does stuff self.view.add_observer('delete:note', self.observer_view_delete_note) self.view.add_observer('select:note', self.observer_view_select_note) self.view.add_observer('change:entry', self.observer_view_change_entry) self.view.add_observer('change:text', self.observer_view_change_text) self.view.add_observer('create:note', self.observer_view_create_note) self.view.add_observer('keep:house', self.observer_view_keep_house) self.view.add_observer('command:markdown', self.observer_view_markdown) self.view.add_observer('command:sync_full', lambda v, et, e: self.sync_full()) self.view.add_observer('command:sync_current_note', self.observer_view_sync_current_note) self.view.add_observer('close', self.observer_view_close) # nn is a list of (key, note) objects nn = self.notes_db.filter_notes() # this will trigger the list_change event self.notes_list_model.set_list(nn) # we'll use this to keep track of the currently selected note # we only use idx, because key could change from right under us. self.selected_note_idx = -1 self.view.select_note(0) # perform full sync with server, and refresh notes list if successful self.sync_full()
class NotesRun(object): def __init__(self): self.db = NotesDB() def get_notes(): query = self.db.get_notes() for data in query: print Note(query[0], query[1], query[2], query[3]) def create_note(self, content, group='General'): note = Note(content, group) self.db.insert_note(note) def get_note(self, note_id): query = self.db.get_note(note_id) note = Note(query[0], query[1], query[2], query[3]) print(note) def create_group(self, group_name): self.db.insert_group(group_name) def show_groups(self): groups = self.db.get_groups(); for group in groups: print(group[0])
def __init__(self, config): # should probably also look in $HOME self.config = config self.config.app_version = VERSION # configure logging module ############################# # first create db directory if it doesn't exist yet. if not os.path.exists(self.config.db_path): os.mkdir(self.config.db_path) log_filename = os.path.join(self.config.db_path, 'nvpy.log') # file will get nuked when it reaches 100kB lhandler = RotatingFileHandler(log_filename, maxBytes=100000, backupCount=1) lhandler.setLevel(logging.DEBUG) lhandler.setFormatter( logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(message)s')) # we get the root logger and configure it logger = logging.getLogger() if self.config.debug == 1: logger.setLevel(logging.DEBUG) logger.addHandler(lhandler) # this will go to the root logger logging.debug('nvpy logging initialized') logging.debug('config read from %s' % (str(self.config.files_read), )) if self.config.sn_username == '': self.config.simplenote_sync = 0 css = self.config.rest_css_path if css: if css.startswith("~/"): # On Mac, paths that start with '~/' aren't found by path.exists css = css.replace("~", os.path.abspath(os.path.expanduser('~')), 1) self.config.rest_css_path = css if not os.path.exists(css): # Couldn't find the user-defined css file. Use docutils css instead. self.config.rest_css_path = None self.notes_list_model = NotesListModel() # create the interface self.view = view.View(self.config, self.notes_list_model) # read our database of notes into memory # and sync with simplenote. try: self.notes_db = NotesDB(self.config) except ReadError, e: emsg = "Please check nvpy.log.\n" + str(e) self.view.show_error('Sync error', emsg) exit(1)
class Controller: """Main application class. """ def __init__(self): # setup appdir if hasattr(sys, 'frozen') and sys.frozen: self.appdir, _ = os.path.split(sys.executable) else: dirname = os.path.dirname(__file__) if dirname and dirname != os.curdir: self.appdir = dirname else: self.appdir = os.getcwd() # make sure it's the full path self.appdir = os.path.abspath(self.appdir) # should probably also look in $HOME self.config = Config(self.appdir) self.config.app_version = self.get_version() # read our database of notes into memory # and sync with simplenote. c = self.config notes_db_config = KeyValueObject(db_path=c.db_path, sn_username=c.sn_username, sn_password=c.sn_password, sort_mode=c.sort_mode) self.notes_db = NotesDB(notes_db_config) self.notes_db.add_observer('synced:note', self.observer_notes_db_synced_note) self.notes_db.add_observer('change:note-status', self.observer_notes_db_change_note_status) self.notes_db.add_observer('progress:sync_full', self.observer_notes_db_sync_full) self.notes_list_model = NotesListModel() # create the interface self.view = view.View(self.config, self.notes_list_model) # we want to be notified when the user does stuff self.view.add_observer('delete:note', self.observer_view_delete_note) self.view.add_observer('select:note', self.observer_view_select_note) self.view.add_observer('change:entry', self.observer_view_change_entry) self.view.add_observer('change:text', self.observer_view_change_text) self.view.add_observer('create:note', self.observer_view_create_note) self.view.add_observer('keep:house', self.observer_view_keep_house) self.view.add_observer('command:markdown', self.observer_view_markdown) self.view.add_observer('command:sync_full', lambda v, et, e: self.sync_full()) self.view.add_observer('command:sync_current_note', self.observer_view_sync_current_note) self.view.add_observer('close', self.observer_view_close) # nn is a list of (key, note) objects nn = self.notes_db.filter_notes() # this will trigger the list_change event self.notes_list_model.set_list(nn) # we'll use this to keep track of the currently selected note # we only use idx, because key could change from right under us. self.selected_note_idx = -1 self.view.select_note(0) # perform full sync with server, and refresh notes list if successful self.sync_full() def get_selected_note_key(self): if self.selected_note_idx >= 0: return self.notes_list_model.list[self.selected_note_idx].key else: return None def get_version(self): return "0.1" def main_loop(self): self.view.main_loop() def observer_notes_db_change_note_status(self, notes_db, evt_type, evt): skey = self.get_selected_note_key() if skey == evt.key: self.view.set_note_status(self.notes_db.get_note_status(skey)) def observer_notes_db_sync_full(self, notes_db, evt_type, evt): print evt.msg self.view.set_status_text(evt.msg) def observer_notes_db_synced_note(self, notes_db, evt_type, evt): """This observer gets called only when a note returns from a sync that's more recent than our most recent mod to that note. """ selected_note_o = self.notes_list_model.list[self.selected_note_idx] # if the note synced back matches our currently selected note, # we overwrite. if selected_note_o.key == evt.lkey: if selected_note_o.note['content'] != evt.old_note['content']: self.view.mute('change:text') self.view.set_text(selected_note_o.note['content']) self.view.unmute('change:text') def observer_view_delete_note(self, view, evt_type, evt): # delete note from notes_db # remove the note from the notes_list_model.list # if these two are not equal, something is not kosher. assert(evt.sel == self.selected_note_idx) # delete the note key = self.get_selected_note_key() self.notes_db.delete_note(key) # easiest now is just to regenerate the list by resetting search string self.view.set_search_entry_text(self.view.get_search_entry_text()) def helper_markdown_to_html(self): if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key c = self.notes_db.get_note_content(key) html = markdown.markdown(c) # create filename based on key fn = os.path.join(self.config.db_path, key + '.html') f = file(fn, 'w') s = """ <html> <head> <meta http-equiv="refresh" content="5"> </head> <body> %s </body> </html> """ % (html,) f.write(s) f.close() return fn def observer_view_markdown(self, view, evt_type, evt): fn = self.helper_markdown_to_html() # turn filename into URI (mac wants this) fn_uri = 'file://' + os.path.abspath(fn) webbrowser.open(fn_uri) def observer_view_keep_house(self, view, evt_type, evt): # queue up all notes that need to be saved nsaved = self.notes_db.save_threaded() nsynced, sync_errors = self.notes_db.sync_to_server_threaded() # get list of note titles, and pass to view to check and fix if necessary qlen = self.notes_db.get_ss_queue_len() if qlen > 0: self.view.set_status_text('Saving and syncing, %d notes in the queue.' % (qlen,)) else: self.view.set_status_text('Idle.') # in continous rendering mode, we also generate a new HTML # the browser, if open, will refresh! if self.view.get_continuous_rendering(): self.helper_markdown_to_html() def observer_view_select_note(self, view, evt_type, evt): self.select_note(evt.sel) def observer_view_sync_current_note(self, view, evt_type, evt): if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key # this call will update our in-memory version if necessary ret = self.notes_db.sync_note_unthreaded(key) if ret and ret[1] == True: self.view.update_selected_note_text( self.notes_db.notes[key]['content']) self.view.set_status_text( 'Synced updated note from server.') elif ret[1] == False: self.view.set_status_text( 'Server had nothing newer for this note.') elif ret is None: self.view.set_status_text( 'Unable to sync with server. Offline?') def observer_view_change_entry(self, view, evt_type, evt): # for each new evt.value coming in, get a new list from the notes_db # and set it in the notes_list_model nn = self.notes_db.filter_notes(evt.value) self.notes_list_model.set_list(nn) # we select note in the view, this will eventually come back to us # in observer_view_select_note self.view.select_note(0) def observer_view_change_text(self, view, evt_type, evt): # get new text and update our database # need local key of currently selected note for this if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key self.notes_db.set_note_content(key, self.view.get_text()) def observer_view_close(self, view, evt_type, evt): # do a last full sync before we go! #self.sync_full() pass def observer_view_create_note(self, view, evt_type, evt): # create the note new_key = self.notes_db.create_note(evt.title) # clear the search entry, this should trigger a new list being returned self.view.set_search_entry_text('') # we should focus on our thingy idx = self.notes_list_model.get_idx(new_key) self.view.select_note(idx) def select_note(self, idx): if idx >= 0: key = self.notes_list_model.list[idx].key c = self.notes_db.get_note_content(key) else: key = None c = '' idx = -1 self.selected_note_idx = idx # when we do this, we don't want the change:text event thanks self.view.mute('change:text') self.view.set_text(c) if key: self.view.set_note_status(self.notes_db.get_note_status(key)) self.view.unmute('change:text') def sync_full(self): try: self.notes_db.sync_full() except SyncError: pass else: # regenerate display list # reselect old selection # put cursor where it used to be. self.view.refresh_notes_list()
def __init__(self): # setup appdir if hasattr(sys, 'frozen') and sys.frozen: self.appdir, _ = os.path.split(sys.executable) else: dirname = os.path.dirname(__file__) if dirname and dirname != os.curdir: self.appdir = dirname else: self.appdir = os.getcwd() # make sure it's the full path self.appdir = os.path.abspath(self.appdir) # should probably also look in $HOME self.config = Config(self.appdir) self.config.app_version = VERSION # configure logging module ############################# # first create db directory if it doesn't exist yet. if not os.path.exists(self.config.db_path): os.mkdir(self.config.db_path) log_filename = os.path.join(self.config.db_path, 'nvpy.log') # file will get nuked when it reaches 100kB lhandler = RotatingFileHandler(log_filename, maxBytes=100000) lhandler.setLevel(logging.DEBUG) lhandler.setFormatter(logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(message)s')) # we get the root logger and configure it logger = logging.getLogger() logger.setLevel(logging.DEBUG) logger.addHandler(lhandler) # this will go to the root logger logging.debug('nvpy logging initialized') logging.debug('config read from %s' % (str(self.config.files_read),)) # read our database of notes into memory # and sync with simplenote. c = self.config notes_db_config = KeyValueObject(db_path=c.db_path, sn_username=c.sn_username, sn_password=c.sn_password, sort_mode=c.sort_mode, pinned_ontop=c.pinned_ontop, case_sensitive=c.case_sensitive, search_tags=c.search_tags) self.notes_db = NotesDB(notes_db_config) self.notes_db.add_observer('synced:note', self.observer_notes_db_synced_note) self.notes_db.add_observer('change:note-status', self.observer_notes_db_change_note_status) self.notes_db.add_observer('progress:sync_full', self.observer_notes_db_sync_full) self.notes_list_model = NotesListModel() # create the interface self.view = view.View(self.config, self.notes_list_model) # we want to be notified when the user does stuff self.view.add_observer('click:notelink', self.observer_view_click_notelink) self.view.add_observer('delete:note', self.observer_view_delete_note) self.view.add_observer('select:note', self.observer_view_select_note) self.view.add_observer('change:entry', self.observer_view_change_entry) self.view.add_observer('change:text', self.observer_view_change_text) self.view.add_observer('change:tags', self.observer_view_change_tags) self.view.add_observer('change:pinned', self.observer_view_change_pinned) self.view.add_observer('create:note', self.observer_view_create_note) self.view.add_observer('keep:house', self.observer_view_keep_house) self.view.add_observer('command:markdown', self.observer_view_markdown) self.view.add_observer('command:rest', self.observer_view_rest) self.view.add_observer('command:sync_full', lambda v, et, e: self.sync_full()) self.view.add_observer('command:sync_current_note', self.observer_view_sync_current_note) self.view.add_observer('close', self.observer_view_close) # nn is a list of (key, note) objects nn = self.notes_db.filter_notes() # this will trigger the list_change event self.notes_list_model.set_list(nn) # we'll use this to keep track of the currently selected note # we only use idx, because key could change from right under us. self.selected_note_idx = -1 self.view.select_note(0) # perform full sync with server, and refresh notes list if successful self.sync_full()
class Controller: """Main application class. """ def __init__(self): # setup appdir if hasattr(sys, 'frozen') and sys.frozen: self.appdir, _ = os.path.split(sys.executable) else: dirname = os.path.dirname(__file__) if dirname and dirname != os.curdir: self.appdir = dirname else: self.appdir = os.getcwd() # make sure it's the full path self.appdir = os.path.abspath(self.appdir) # should probably also look in $HOME self.config = Config(self.appdir) self.config.app_version = VERSION # configure logging module ############################# # first create db directory if it doesn't exist yet. if not os.path.exists(self.config.db_path): os.mkdir(self.config.db_path) log_filename = os.path.join(self.config.db_path, 'nvpy.log') # file will get nuked when it reaches 100kB lhandler = RotatingFileHandler(log_filename, maxBytes=100000) lhandler.setLevel(logging.DEBUG) lhandler.setFormatter(logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(message)s')) # we get the root logger and configure it logger = logging.getLogger() logger.setLevel(logging.DEBUG) logger.addHandler(lhandler) # this will go to the root logger logging.debug('nvpy logging initialized') logging.debug('config read from %s' % (str(self.config.files_read),)) # read our database of notes into memory # and sync with simplenote. c = self.config notes_db_config = KeyValueObject(db_path=c.db_path, sn_username=c.sn_username, sn_password=c.sn_password, sort_mode=c.sort_mode, pinned_ontop=c.pinned_ontop, case_sensitive=c.case_sensitive, search_tags=c.search_tags) self.notes_db = NotesDB(notes_db_config) self.notes_db.add_observer('synced:note', self.observer_notes_db_synced_note) self.notes_db.add_observer('change:note-status', self.observer_notes_db_change_note_status) self.notes_db.add_observer('progress:sync_full', self.observer_notes_db_sync_full) self.notes_list_model = NotesListModel() # create the interface self.view = view.View(self.config, self.notes_list_model) # we want to be notified when the user does stuff self.view.add_observer('click:notelink', self.observer_view_click_notelink) self.view.add_observer('delete:note', self.observer_view_delete_note) self.view.add_observer('select:note', self.observer_view_select_note) self.view.add_observer('change:entry', self.observer_view_change_entry) self.view.add_observer('change:text', self.observer_view_change_text) self.view.add_observer('change:tags', self.observer_view_change_tags) self.view.add_observer('change:pinned', self.observer_view_change_pinned) self.view.add_observer('create:note', self.observer_view_create_note) self.view.add_observer('keep:house', self.observer_view_keep_house) self.view.add_observer('command:markdown', self.observer_view_markdown) self.view.add_observer('command:rest', self.observer_view_rest) self.view.add_observer('command:sync_full', lambda v, et, e: self.sync_full()) self.view.add_observer('command:sync_current_note', self.observer_view_sync_current_note) self.view.add_observer('close', self.observer_view_close) # nn is a list of (key, note) objects nn = self.notes_db.filter_notes() # this will trigger the list_change event self.notes_list_model.set_list(nn) # we'll use this to keep track of the currently selected note # we only use idx, because key could change from right under us. self.selected_note_idx = -1 self.view.select_note(0) # perform full sync with server, and refresh notes list if successful self.sync_full() def get_selected_note_key(self): if self.selected_note_idx >= 0: return self.notes_list_model.list[self.selected_note_idx].key else: return None def main_loop(self): if not self.config.files_read: self.view.show_warning('No config file', 'Could not read any configuration files. See https://github.com/cpbotha/nvpy for details.') elif not self.config.ok: wmsg = ('Please rename [default] to [nvpy] in %s. ' + \ 'Config file format changed after nvPY 0.8.') % \ (str(self.config.files_read),) self.view.show_warning('Rename config section', wmsg) self.view.main_loop() def observer_notes_db_change_note_status(self, notes_db, evt_type, evt): skey = self.get_selected_note_key() if skey == evt.key: self.view.set_note_status(self.notes_db.get_note_status(skey)) def observer_notes_db_sync_full(self, notes_db, evt_type, evt): logging.debug(evt.msg) self.view.set_status_text(evt.msg) def observer_notes_db_synced_note(self, notes_db, evt_type, evt): """This observer gets called only when a note returns from a sync that's more recent than our most recent mod to that note. """ selected_note_o = self.notes_list_model.list[self.selected_note_idx] # if the note synced back matches our currently selected note, # we overwrite. if selected_note_o.key == evt.lkey: if selected_note_o.note['content'] != evt.old_note['content']: self.view.mute_note_data_changes() # in this case, we want to keep the user's undo buffer so that they # can undo synced back changes if they would want to. self.view.set_note_data(selected_note_o.note, reset_undo=False) self.view.unmute_note_data_changes() def observer_view_click_notelink(self, view, evt_type, note_name): # find note_name in titles, try to jump to that note # if not in current list, change search string in case # it's somewhere else # FIXME: implement find_note_by_name idx = self.view.select_note_by_name(note_name) if idx < 0: # this means a note with that name was not found # because nvpy kicks ass, it then assumes the contents of [[]] # to be a new regular expression to search for in the notes db. self.view.set_search_entry_text(note_name) def observer_view_delete_note(self, view, evt_type, evt): # delete note from notes_db # remove the note from the notes_list_model.list # if these two are not equal, something is not kosher. assert(evt.sel == self.selected_note_idx) # first get key of note that is to be deleted key = self.get_selected_note_key() # then try to select after the one that is to be deleted nidx = evt.sel + 1 if nidx >= 0 and nidx < self.view.get_number_of_notes(): self.view.select_note(nidx) # finally delete the note self.notes_db.delete_note(key) # easiest now is just to regenerate the list by resetting search string # if the note after the deleted one is already selected, this will # simply keep that selection! self.view.set_search_entry_text(self.view.get_search_entry_text()) def helper_markdown_to_html(self): if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key c = self.notes_db.get_note_content(key) if HAVE_MARKDOWN: html = markdown.markdown(c) else: html = "<p>python markdown not installed, required for rendering to HTML.</p>" html += "<p>Please install with \"pip install markdown\".</p>" # create filename based on key fn = os.path.join(self.config.db_path, key + '.html') f = codecs.open(fn, mode='wb', encoding='utf-8') s = u""" <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="refresh" content="5"> </head> <body> %s </body> </html> """ % (html,) f.write(s) f.close() return fn def helper_rest_to_html(self): if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key c = self.notes_db.get_note_content(key) if HAVE_DOCUTILS: # this gives the whole document html = docutils.core.publish_string(c, writer_name='html') # publish_parts("*anurag*",writer_name='html')['body'] # gives just the desired part of the tree else: html = "<p>python docutils not installed, required for rendering reST to HTML.</p>" html += "<p>Please install with \"pip install docutils\".</p>" # create filename based on key fn = os.path.join(self.config.db_path, key + '_rest.html') f = codecs.open(fn, mode='wb', encoding='utf-8') # explicit decode from utf8 into unicode object. If we don't # specify utf8, python falls back to default ascii and then we get # "'ascii' codec can't decode byte" error s = u""" %s """ % (unicode(html, 'utf8'),) f.write(s) f.close() return fn def observer_view_markdown(self, view, evt_type, evt): fn = self.helper_markdown_to_html() # turn filename into URI (mac wants this) fn_uri = 'file://' + os.path.abspath(fn) webbrowser.open(fn_uri) def observer_view_rest(self, view, evt_type, evt): fn = self.helper_rest_to_html() # turn filename into URI (mac wants this) fn_uri = 'file://' + os.path.abspath(fn) webbrowser.open(fn_uri) def helper_save_sync_msg(self): # Saving 2 notes. Syncing 3 notes, waiting for simplenote server. # All notes saved. All notes synced. saven = self.notes_db.get_save_queue_len() syncn = self.notes_db.get_sync_queue_len() wfsn = self.notes_db.waiting_for_simplenote savet = 'Saving %d notes.' % (saven,) if saven > 0 else ''; synct = 'Waiting to sync %d notes.' % (syncn,) if syncn > 0 else ''; wfsnt = 'Syncing with simplenote server.' if wfsn else ''; return ' '.join([i for i in [savet, synct, wfsnt] if i]) def observer_view_keep_house(self, view, evt_type, evt): # queue up all notes that need to be saved nsaved = self.notes_db.save_threaded() nsynced, sync_errors = self.notes_db.sync_to_server_threaded() msg = self.helper_save_sync_msg() if sync_errors: msg = ' '.join([i for i in [msg, 'Could not connect to simplenote server.'] if i]) self.view.set_status_text(msg) # in continous rendering mode, we also generate a new HTML # the browser, if open, will refresh! if self.view.get_continuous_rendering(): self.helper_markdown_to_html() def observer_view_select_note(self, view, evt_type, evt): self.select_note(evt.sel) def observer_view_sync_current_note(self, view, evt_type, evt): if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key # this call will update our in-memory version if necessary ret = self.notes_db.sync_note_unthreaded(key) if ret and ret[1] == True: self.view.update_selected_note_data( self.notes_db.notes[key]) self.view.set_status_text( 'Synced updated note from server.') elif ret[1] == False: self.view.set_status_text( 'Server had nothing newer for this note.') elif ret is None: self.view.set_status_text( 'Unable to sync with server. Offline?') def observer_view_change_entry(self, view, evt_type, evt): # store the currently selected note key k = self.get_selected_note_key() # for each new evt.value coming in, get a new list from the notes_db # and set it in the notes_list_model nn = self.notes_db.filter_notes(evt.value) self.notes_list_model.set_list(nn) idx = self.notes_list_model.get_idx(k) if idx < 0: self.view.select_note(0) # the user is typing, but her previously selected note is # not in the new filtered list. as a convenience, we move # the text in the text widget so it's on the first # occurrence of the search string, IF there's such an # occurrence. self.view.see_first_search_instance() else: # we don't want new text to be implanted (YET) so we keep this silent # if it does turn out to be new note content, this will be handled # a few lines down. self.view.select_note(idx, silent=True) # but of course we DO have to record the possibly new IDX!! self.selected_note_idx = idx # see if the note has been updated (content, tags, pin) new_note = self.notes_db.get_note(k) # check if the currently selected note is different from the one # currently being displayed. this could happen if a sync gets # a new note of the server to replace the currently displayed one. if self.view.is_note_different(new_note): logging.debug("Currently selected note %s replaced by newer from server." % (k,)) # carefully update currently selected note # restore cursor position, search and link highlights self.view.update_selected_note_data(new_note) else: # we have a new search string, but did not make any text changes # so we have to update the search highlighting here. (usually # text changes trigger this) self.view.activate_search_string_highlights() def observer_view_change_text(self, view, evt_type, evt): # get new text and update our database # need local key of currently selected note for this if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key self.notes_db.set_note_content(key, self.view.get_text()) def observer_view_change_tags(self, view, evt_type, evt): # get new text and update our database # need local key of currently selected note for this if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key self.notes_db.set_note_tags(key, evt.value) def observer_view_change_pinned(self, view, evt_type, evt): # get new text and update our database # need local key of currently selected note for this if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key self.notes_db.set_note_pinned(key, evt.value) def observer_view_close(self, view, evt_type, evt): # check that everything has been saved and synced before exiting # first make sure all our queues are up to date self.notes_db.save_threaded() self.notes_db.sync_to_server_threaded(wait_for_idle=False) # then check all queues saven = self.notes_db.get_save_queue_len() syncn = self.notes_db.get_sync_queue_len() wfsn = self.notes_db.waiting_for_simplenote # if there's still something to do, warn the user. if saven or syncn or wfsn: msg = "Are you sure you want to exit? I'm still busy: " + self.helper_save_sync_msg() really_want_to_exit = self.view.askyesno("Confirm exit", msg) if really_want_to_exit: self.view.close() else: self.view.close() def observer_view_create_note(self, view, evt_type, evt): # create the note new_key = self.notes_db.create_note(evt.title) # clear the search entry, this should trigger a new list being returned self.view.set_search_entry_text('') # we should focus on our thingy idx = self.notes_list_model.get_idx(new_key) self.view.select_note(idx) def select_note(self, idx): if idx >= 0: key = self.notes_list_model.list[idx].key note = self.notes_db.get_note(key) else: key = None note = None idx = -1 self.selected_note_idx = idx # when we do this, we don't want the change:{text,tags,pinned} events # because those should only fire when they are changed through the UI self.view.mute_note_data_changes() self.view.set_note_data(note) if key: self.view.set_note_status(self.notes_db.get_note_status(key)) self.view.unmute_note_data_changes() def sync_full(self): try: sync_from_server_errors = self.notes_db.sync_full() except SyncError as e: self.view.show_error('Sync error', e.message) else: # regenerate display list # reselect old selection # put cursor where it used to be. self.view.refresh_notes_list() if sync_from_server_errors > 0: self.view.show_error('Error syncing notes from server', 'Error syncing %d notes from server. Please check nvpy.log for details.' % (sync_from_server_errors,))
def __init__(self): # setup appdir if hasattr(sys, 'frozen') and sys.frozen: self.appdir, _ = os.path.split(sys.executable) else: dirname = os.path.dirname(__file__) if dirname and dirname != os.curdir: self.appdir = dirname else: self.appdir = os.getcwd() # make sure it's the full path self.appdir = os.path.abspath(self.appdir) # should probably also look in $HOME self.config = Config(self.appdir) self.config.app_version = self.get_version() # configure logging module ############################# # first create db directory if it doesn't exist yet. if not os.path.exists(self.config.db_path): os.mkdir(self.config.db_path) log_filename = os.path.join(self.config.db_path, 'nvpy.log') # file will get nuked when it reaches 100kB lhandler = RotatingFileHandler(log_filename, maxBytes=100000) lhandler.setLevel(logging.DEBUG) lhandler.setFormatter( logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(message)s')) # we get the root logger and configure it logger = logging.getLogger() logger.setLevel(logging.DEBUG) logger.addHandler(lhandler) # this will go to the root logger logging.debug('nvpy logging initialized') # read our database of notes into memory # and sync with simplenote. c = self.config notes_db_config = KeyValueObject(db_path=c.db_path, sn_username=c.sn_username, sn_password=c.sn_password, sort_mode=c.sort_mode) self.notes_db = NotesDB(notes_db_config) self.notes_db.add_observer('synced:note', self.observer_notes_db_synced_note) self.notes_db.add_observer('change:note-status', self.observer_notes_db_change_note_status) self.notes_db.add_observer('progress:sync_full', self.observer_notes_db_sync_full) self.notes_list_model = NotesListModel() # create the interface self.view = view.View(self.config, self.notes_list_model) # we want to be notified when the user does stuff self.view.add_observer('click:notelink', self.observer_view_click_notelink) self.view.add_observer('delete:note', self.observer_view_delete_note) self.view.add_observer('select:note', self.observer_view_select_note) self.view.add_observer('change:entry', self.observer_view_change_entry) self.view.add_observer('change:text', self.observer_view_change_text) self.view.add_observer('create:note', self.observer_view_create_note) self.view.add_observer('keep:house', self.observer_view_keep_house) self.view.add_observer('command:markdown', self.observer_view_markdown) self.view.add_observer('command:rest', self.observer_view_rest) self.view.add_observer('command:sync_full', lambda v, et, e: self.sync_full()) self.view.add_observer('command:sync_current_note', self.observer_view_sync_current_note) self.view.add_observer('close', self.observer_view_close) # nn is a list of (key, note) objects nn = self.notes_db.filter_notes() # this will trigger the list_change event self.notes_list_model.set_list(nn) # we'll use this to keep track of the currently selected note # we only use idx, because key could change from right under us. self.selected_note_idx = -1 self.view.select_note(0) # perform full sync with server, and refresh notes list if successful self.sync_full()
class Controller: """Main application class. """ def __init__(self): # setup appdir if hasattr(sys, 'frozen') and sys.frozen: self.appdir, _ = os.path.split(sys.executable) else: dirname = os.path.dirname(__file__) if dirname and dirname != os.curdir: self.appdir = dirname else: self.appdir = os.getcwd() # make sure it's the full path self.appdir = os.path.abspath(self.appdir) # should probably also look in $HOME self.config = Config(self.appdir) self.config.app_version = self.get_version() # configure logging module ############################# # first create db directory if it doesn't exist yet. if not os.path.exists(self.config.db_path): os.mkdir(self.config.db_path) log_filename = os.path.join(self.config.db_path, 'nvpy.log') # file will get nuked when it reaches 100kB lhandler = RotatingFileHandler(log_filename, maxBytes=100000) lhandler.setLevel(logging.DEBUG) lhandler.setFormatter( logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(message)s')) # we get the root logger and configure it logger = logging.getLogger() logger.setLevel(logging.DEBUG) logger.addHandler(lhandler) # this will go to the root logger logging.debug('nvpy logging initialized') # read our database of notes into memory # and sync with simplenote. c = self.config notes_db_config = KeyValueObject(db_path=c.db_path, sn_username=c.sn_username, sn_password=c.sn_password, sort_mode=c.sort_mode) self.notes_db = NotesDB(notes_db_config) self.notes_db.add_observer('synced:note', self.observer_notes_db_synced_note) self.notes_db.add_observer('change:note-status', self.observer_notes_db_change_note_status) self.notes_db.add_observer('progress:sync_full', self.observer_notes_db_sync_full) self.notes_list_model = NotesListModel() # create the interface self.view = view.View(self.config, self.notes_list_model) # we want to be notified when the user does stuff self.view.add_observer('click:notelink', self.observer_view_click_notelink) self.view.add_observer('delete:note', self.observer_view_delete_note) self.view.add_observer('select:note', self.observer_view_select_note) self.view.add_observer('change:entry', self.observer_view_change_entry) self.view.add_observer('change:text', self.observer_view_change_text) self.view.add_observer('create:note', self.observer_view_create_note) self.view.add_observer('keep:house', self.observer_view_keep_house) self.view.add_observer('command:markdown', self.observer_view_markdown) self.view.add_observer('command:rest', self.observer_view_rest) self.view.add_observer('command:sync_full', lambda v, et, e: self.sync_full()) self.view.add_observer('command:sync_current_note', self.observer_view_sync_current_note) self.view.add_observer('close', self.observer_view_close) # nn is a list of (key, note) objects nn = self.notes_db.filter_notes() # this will trigger the list_change event self.notes_list_model.set_list(nn) # we'll use this to keep track of the currently selected note # we only use idx, because key could change from right under us. self.selected_note_idx = -1 self.view.select_note(0) # perform full sync with server, and refresh notes list if successful self.sync_full() def get_selected_note_key(self): if self.selected_note_idx >= 0: return self.notes_list_model.list[self.selected_note_idx].key else: return None def get_version(self): return "0.8" def main_loop(self): self.view.main_loop() def observer_notes_db_change_note_status(self, notes_db, evt_type, evt): skey = self.get_selected_note_key() if skey == evt.key: self.view.set_note_status(self.notes_db.get_note_status(skey)) def observer_notes_db_sync_full(self, notes_db, evt_type, evt): logging.debug(evt.msg) self.view.set_status_text(evt.msg) def observer_notes_db_synced_note(self, notes_db, evt_type, evt): """This observer gets called only when a note returns from a sync that's more recent than our most recent mod to that note. """ selected_note_o = self.notes_list_model.list[self.selected_note_idx] # if the note synced back matches our currently selected note, # we overwrite. if selected_note_o.key == evt.lkey: if selected_note_o.note['content'] != evt.old_note['content']: self.view.mute('change:text') # in this case, we want to keep the user's undo buffer so that they # can undo synced back changes if they would want to. self.view.set_text(selected_note_o.note['content'], reset_undo=False) self.view.unmute('change:text') def observer_view_click_notelink(self, view, evt_type, note_name): # find note_name in titles, try to jump to that note # if not in current list, change search string in case # it's somewhere else # FIXME: implement find_note_by_name idx = self.view.select_note_by_name(note_name) if idx < 0: # this means a note with that name was not found # because nvpy kicks ass, it then assumes the contents of [[]] # to be a new regular expression to search for in the notes db. self.view.set_search_entry_text(note_name) def observer_view_delete_note(self, view, evt_type, evt): # delete note from notes_db # remove the note from the notes_list_model.list # if these two are not equal, something is not kosher. assert (evt.sel == self.selected_note_idx) # delete the note key = self.get_selected_note_key() self.notes_db.delete_note(key) # easiest now is just to regenerate the list by resetting search string self.view.set_search_entry_text(self.view.get_search_entry_text()) def helper_markdown_to_html(self): if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key c = self.notes_db.get_note_content(key) if HAVE_MARKDOWN: html = markdown.markdown(c) else: html = "<p>python markdown not installed, required for rendering to HTML.</p>" html += "<p>Please install with \"pip install markdown\".</p>" # create filename based on key fn = os.path.join(self.config.db_path, key + '.html') f = codecs.open(fn, mode='wb', encoding='utf-8') s = u""" <html> <head> <meta http-equiv="refresh" content="5"> </head> <body> %s </body> </html> """ % (html, ) f.write(s) f.close() return fn def helper_rest_to_html(self): if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key c = self.notes_db.get_note_content(key) if HAVE_DOCUTILS: # this gives the whole document html = docutils.core.publish_string(c, writer_name='html') # publish_parts("*anurag*",writer_name='html')['body'] # gives just the desired part of the tree else: html = "<p>python docutils not installed, required for rendering reST to HTML.</p>" html += "<p>Please install with \"pip install docutils\".</p>" # create filename based on key fn = os.path.join(self.config.db_path, key + '_rest.html') f = codecs.open(fn, mode='wb', encoding='utf-8') # we keep this for later, in case we want to modify rest output # or combine it with our own headers. s = u""" %s """ % (html, ) f.write(s) f.close() return fn def observer_view_markdown(self, view, evt_type, evt): fn = self.helper_markdown_to_html() # turn filename into URI (mac wants this) fn_uri = 'file://' + os.path.abspath(fn) webbrowser.open(fn_uri) def observer_view_rest(self, view, evt_type, evt): fn = self.helper_rest_to_html() # turn filename into URI (mac wants this) fn_uri = 'file://' + os.path.abspath(fn) webbrowser.open(fn_uri) def helper_save_sync_msg(self): # Saving 2 notes. Syncing 3 notes, waiting for simplenote server. # All notes saved. All notes synced. saven = self.notes_db.get_save_queue_len() syncn = self.notes_db.get_sync_queue_len() wfsn = self.notes_db.waiting_for_simplenote savet = 'Saving %d notes.' % (saven, ) if saven > 0 else '' synct = 'Waiting to sync %d notes.' % (syncn, ) if syncn > 0 else '' wfsnt = 'Syncing with simplenote server.' if wfsn else '' return ' '.join([i for i in [savet, synct, wfsnt] if i]) def observer_view_keep_house(self, view, evt_type, evt): # queue up all notes that need to be saved nsaved = self.notes_db.save_threaded() nsynced, sync_errors = self.notes_db.sync_to_server_threaded() msg = self.helper_save_sync_msg() if sync_errors: msg = ' '.join([ i for i in [msg, 'Could not connect to simplenote server.'] if i ]) self.view.set_status_text(msg) # in continous rendering mode, we also generate a new HTML # the browser, if open, will refresh! if self.view.get_continuous_rendering(): self.helper_markdown_to_html() def observer_view_select_note(self, view, evt_type, evt): self.select_note(evt.sel) def observer_view_sync_current_note(self, view, evt_type, evt): if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key # this call will update our in-memory version if necessary ret = self.notes_db.sync_note_unthreaded(key) if ret and ret[1] == True: self.view.update_selected_note_text( self.notes_db.notes[key]['content']) self.view.set_status_text('Synced updated note from server.') elif ret[1] == False: self.view.set_status_text( 'Server had nothing newer for this note.') elif ret is None: self.view.set_status_text( 'Unable to sync with server. Offline?') def observer_view_change_entry(self, view, evt_type, evt): # store the currently selected note key k = self.get_selected_note_key() # for each new evt.value coming in, get a new list from the notes_db # and set it in the notes_list_model nn = self.notes_db.filter_notes(evt.value) self.notes_list_model.set_list(nn) idx = self.notes_list_model.get_idx(k) if idx < 0: self.view.select_note(0) # the user is typing, but her previously selected note is # not in the new filtered list. as a convenience, we move # the text in the text widget so it's on the first # occurrence of the search string, IF there's such an # occurrenc. self.view.see_first_search_instance() else: # we don't want new text to be implanted so we keep this silent self.view.select_note(idx, silent=True) # but of course we DO have to record the possibly new IDX!! self.selected_note_idx = idx # we have a new search string, but did not make any text changes # so we have to update the search highlighting here. (usually # text changes trigger this) self.view.activate_search_string_highlights() def observer_view_change_text(self, view, evt_type, evt): # get new text and update our database # need local key of currently selected note for this if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key self.notes_db.set_note_content(key, self.view.get_text()) def observer_view_close(self, view, evt_type, evt): # check that everything has been saved and synced before exiting # first make sure all our queues are up to date self.notes_db.save_threaded() self.notes_db.sync_to_server_threaded(wait_for_idle=False) # then check all queues saven = self.notes_db.get_save_queue_len() syncn = self.notes_db.get_sync_queue_len() wfsn = self.notes_db.waiting_for_simplenote # if there's still something to do, warn the user. if saven or syncn or wfsn: msg = "Are you sure you want to exit? I'm still busy: " + self.helper_save_sync_msg( ) really_want_to_exit = self.view.askyesno("Confirm exit", msg) if really_want_to_exit: self.view.close() else: self.view.close() def observer_view_create_note(self, view, evt_type, evt): # create the note new_key = self.notes_db.create_note(evt.title) # clear the search entry, this should trigger a new list being returned self.view.set_search_entry_text('') # we should focus on our thingy idx = self.notes_list_model.get_idx(new_key) self.view.select_note(idx) def select_note(self, idx): if idx >= 0: key = self.notes_list_model.list[idx].key c = self.notes_db.get_note_content(key) else: key = None c = '' idx = -1 self.selected_note_idx = idx # when we do this, we don't want the change:text event thanks self.view.mute('change:text') self.view.set_text(c) if key: self.view.set_note_status(self.notes_db.get_note_status(key)) self.view.unmute('change:text') def sync_full(self): try: self.notes_db.sync_full() except SyncError: pass else: # regenerate display list # reselect old selection # put cursor where it used to be. self.view.refresh_notes_list()
class Controller: """Main application class. """ def __init__(self): # setup appdir if hasattr(sys, 'frozen') and sys.frozen: self.appdir, _ = os.path.split(sys.executable) else: dirname = os.path.dirname(__file__) if dirname and dirname != os.curdir: self.appdir = dirname else: self.appdir = os.getcwd() # make sure it's the full path self.appdir = os.path.abspath(self.appdir) # should probably also look in $HOME self.config = Config(self.appdir) self.config.app_version = VERSION # configure logging module ############################# # first create db directory if it doesn't exist yet. if not os.path.exists(self.config.db_path): os.mkdir(self.config.db_path) log_filename = os.path.join(self.config.db_path, 'nvpy.log') # file will get nuked when it reaches 100kB lhandler = RotatingFileHandler(log_filename, maxBytes=100000) lhandler.setLevel(logging.DEBUG) lhandler.setFormatter( logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(message)s')) # we get the root logger and configure it logger = logging.getLogger() logger.setLevel(logging.DEBUG) logger.addHandler(lhandler) # this will go to the root logger logging.debug('nvpy logging initialized') logging.debug('config read from %s' % (str(self.config.files_read), )) if self.config.sn_username == '': self.config.simplenote_sync = 0 css = self.config.rest_css_path if css: if css.startswith("~/"): # On Mac, paths that start with '~/' aren't found by path.exists css = css.replace("~", os.path.abspath(os.path.expanduser('~')), 1) self.config.rest_css_path = css if not os.path.exists(css): # Couldn't find the user-defined css file. Use docutils css instead. self.config.rest_css_path = None # read our database of notes into memory # and sync with simplenote. c = self.config notes_db_config = KeyValueObject(db_path=c.db_path, sn_username=c.sn_username, sn_password=c.sn_password, sort_mode=c.sort_mode, pinned_ontop=c.pinned_ontop, case_sensitive=c.case_sensitive, search_tags=c.search_tags, notes_as_txt=c.notes_as_txt, txt_path=c.txt_path, simplenote_sync=c.simplenote_sync) self.notes_db = NotesDB(notes_db_config) self.notes_db.add_observer('synced:note', self.observer_notes_db_synced_note) self.notes_db.add_observer('change:note-status', self.observer_notes_db_change_note_status) if self.config.simplenote_sync: self.notes_db.add_observer('progress:sync_full', self.observer_notes_db_sync_full) self.notes_list_model = NotesListModel() # create the interface self.view = view.View(self.config, self.notes_list_model) # we want to be notified when the user does stuff self.view.add_observer('click:notelink', self.observer_view_click_notelink) self.view.add_observer('delete:note', self.observer_view_delete_note) self.view.add_observer('select:note', self.observer_view_select_note) self.view.add_observer('change:entry', self.observer_view_change_entry) self.view.add_observer('change:text', self.observer_view_change_text) self.view.add_observer('change:tags', self.observer_view_change_tags) self.view.add_observer('change:pinned', self.observer_view_change_pinned) self.view.add_observer('create:note', self.observer_view_create_note) self.view.add_observer('keep:house', self.observer_view_keep_house) self.view.add_observer('command:markdown', self.observer_view_markdown) self.view.add_observer('command:rest', self.observer_view_rest) if self.config.simplenote_sync: self.view.add_observer('command:sync_full', lambda v, et, e: self.sync_full()) self.view.add_observer('command:sync_current_note', self.observer_view_sync_current_note) self.view.add_observer('close', self.observer_view_close) # nn is a list of (key, note) objects nn = self.notes_db.filter_notes() # this will trigger the list_change event self.notes_list_model.set_list(nn) # we'll use this to keep track of the currently selected note # we only use idx, because key could change from right under us. self.selected_note_idx = -1 self.view.select_note(0) # perform full sync with server, and refresh notes list if successful if self.config.simplenote_sync: self.sync_full() def get_selected_note_key(self): if self.selected_note_idx >= 0: return self.notes_list_model.list[self.selected_note_idx].key else: return None def main_loop(self): if not self.config.files_read: self.view.show_warning( 'No config file', 'Could not read any configuration files. See https://github.com/cpbotha/nvpy for details.' ) elif not self.config.ok: wmsg = ('Please rename [default] to [nvpy] in %s. ' + \ 'Config file format changed after nvPY 0.8.') % \ (str(self.config.files_read),) self.view.show_warning('Rename config section', wmsg) self.view.main_loop() def observer_notes_db_change_note_status(self, notes_db, evt_type, evt): skey = self.get_selected_note_key() if skey == evt.key: self.view.set_note_status(self.notes_db.get_note_status(skey)) def observer_notes_db_sync_full(self, notes_db, evt_type, evt): logging.debug(evt.msg) self.view.set_status_text(evt.msg) def observer_notes_db_synced_note(self, notes_db, evt_type, evt): """This observer gets called only when a note returns from a sync that's more recent than our most recent mod to that note. """ selected_note_o = self.notes_list_model.list[self.selected_note_idx] # if the note synced back matches our currently selected note, # we overwrite. if selected_note_o.key == evt.lkey: if selected_note_o.note['content'] != evt.old_note['content']: self.view.mute_note_data_changes() # in this case, we want to keep the user's undo buffer so that they # can undo synced back changes if they would want to. self.view.set_note_data(selected_note_o.note, reset_undo=False) self.view.unmute_note_data_changes() def observer_view_click_notelink(self, view, evt_type, note_name): # find note_name in titles, try to jump to that note # if not in current list, change search string in case # it's somewhere else # FIXME: implement find_note_by_name idx = self.view.select_note_by_name(note_name) if idx < 0: # this means a note with that name was not found # because nvpy kicks ass, it then assumes the contents of [[]] # to be a new regular expression to search for in the notes db. self.view.set_search_entry_text(note_name) def observer_view_delete_note(self, view, evt_type, evt): # delete note from notes_db # remove the note from the notes_list_model.list # if these two are not equal, something is not kosher. assert (evt.sel == self.selected_note_idx) # first get key of note that is to be deleted key = self.get_selected_note_key() # then try to select after the one that is to be deleted nidx = evt.sel + 1 if nidx >= 0 and nidx < self.view.get_number_of_notes(): self.view.select_note(nidx) # finally delete the note self.notes_db.delete_note(key) # easiest now is just to regenerate the list by resetting search string # if the note after the deleted one is already selected, this will # simply keep that selection! self.view.set_search_entry_text(self.view.get_search_entry_text()) def helper_markdown_to_html(self): if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key c = self.notes_db.get_note_content(key) logging.debug("Trying to convert %s to html." % (key, )) if HAVE_MARKDOWN: logging.debug("Convert note %s to html." % (key, )) html = markdown.markdown(c) logging.debug("Convert done.") else: logging.debug("Markdown not installed.") html = "<p>python markdown not installed, required for rendering to HTML.</p>" html += "<p>Please install with \"pip install markdown\".</p>" # create filename based on key fn = os.path.join(self.config.db_path, key + '.html') f = codecs.open(fn, mode='wb', encoding='utf-8') s = u""" <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="refresh" content="5"> </head> <body> %s </body> </html> """ % (html, ) f.write(s) f.close() return fn def helper_rest_to_html(self): if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key c = self.notes_db.get_note_content(key) if HAVE_DOCUTILS: settings = {} if self.config.rest_css_path: settings['stylesheet_path'] = self.config.rest_css_path # this gives the whole document html = docutils.core.publish_string( c, writer_name='html', settings_overrides=settings) # publish_parts("*anurag*",writer_name='html')['body'] # gives just the desired part of the tree else: html = "<p>python docutils not installed, required for rendering reST to HTML.</p>" html += "<p>Please install with \"pip install docutils\".</p>" # create filename based on key fn = os.path.join(self.config.db_path, key + '_rest.html') f = codecs.open(fn, mode='wb', encoding='utf-8') # explicit decode from utf8 into unicode object. If we don't # specify utf8, python falls back to default ascii and then we get # "'ascii' codec can't decode byte" error s = u""" %s """ % (unicode(html, 'utf8'), ) f.write(s) f.close() return fn def observer_view_markdown(self, view, evt_type, evt): fn = self.helper_markdown_to_html() # turn filename into URI (mac wants this) fn_uri = 'file://' + os.path.abspath(fn) webbrowser.open(fn_uri) def observer_view_rest(self, view, evt_type, evt): fn = self.helper_rest_to_html() # turn filename into URI (mac wants this) fn_uri = 'file://' + os.path.abspath(fn) webbrowser.open(fn_uri) def helper_save_sync_msg(self): # Saving 2 notes. Syncing 3 notes, waiting for simplenote server. # All notes saved. All notes synced. saven = self.notes_db.get_save_queue_len() if self.config.simplenote_sync: syncn = self.notes_db.get_sync_queue_len() wfsn = self.notes_db.waiting_for_simplenote else: syncn = wfsn = 0 savet = 'Saving %d notes.' % (saven, ) if saven > 0 else '' synct = 'Waiting to sync %d notes.' % (syncn, ) if syncn > 0 else '' wfsnt = 'Syncing with simplenote server.' if wfsn else '' return ' '.join([i for i in [savet, synct, wfsnt] if i]) def observer_view_keep_house(self, view, evt_type, evt): # queue up all notes that need to be saved nsaved = self.notes_db.save_threaded() msg = self.helper_save_sync_msg() if self.config.simplenote_sync: nsynced, sync_errors = self.notes_db.sync_to_server_threaded() if sync_errors: msg = ' '.join([ i for i in [msg, 'Could not connect to simplenote server.'] if i ]) self.view.set_status_text(msg) # in continous rendering mode, we also generate a new HTML # the browser, if open, will refresh! if self.view.get_continuous_rendering(): self.helper_markdown_to_html() def observer_view_select_note(self, view, evt_type, evt): self.select_note(evt.sel) def observer_view_sync_current_note(self, view, evt_type, evt): if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key # this call will update our in-memory version if necessary ret = self.notes_db.sync_note_unthreaded(key) if ret and ret[1] == True: self.view.update_selected_note_data(self.notes_db.notes[key]) self.view.set_status_text('Synced updated note from server.') elif ret[1] == False: self.view.set_status_text( 'Server had nothing newer for this note.') elif ret is None: self.view.set_status_text( 'Unable to sync with server. Offline?') def observer_view_change_entry(self, view, evt_type, evt): # store the currently selected note key k = self.get_selected_note_key() # for each new evt.value coming in, get a new list from the notes_db # and set it in the notes_list_model nn = self.notes_db.filter_notes(evt.value) self.notes_list_model.set_list(nn) idx = self.notes_list_model.get_idx(k) if idx < 0: self.view.select_note(0) # the user is typing, but her previously selected note is # not in the new filtered list. as a convenience, we move # the text in the text widget so it's on the first # occurrence of the search string, IF there's such an # occurrence. self.view.see_first_search_instance() else: # we don't want new text to be implanted (YET) so we keep this silent # if it does turn out to be new note content, this will be handled # a few lines down. self.view.select_note(idx, silent=True) # but of course we DO have to record the possibly new IDX!! self.selected_note_idx = idx # see if the note has been updated (content, tags, pin) new_note = self.notes_db.get_note(k) # check if the currently selected note is different from the one # currently being displayed. this could happen if a sync gets # a new note of the server to replace the currently displayed one. if self.view.is_note_different(new_note): logging.debug( "Currently selected note %s replaced by newer from server." % (k, )) # carefully update currently selected note # restore cursor position, search and link highlights self.view.update_selected_note_data(new_note) else: # we have a new search string, but did not make any text changes # so we have to update the search highlighting here. (usually # text changes trigger this) self.view.activate_search_string_highlights() def observer_view_change_text(self, view, evt_type, evt): # get new text and update our database # need local key of currently selected note for this if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key self.notes_db.set_note_content(key, self.view.get_text()) def observer_view_change_tags(self, view, evt_type, evt): # get new text and update our database # need local key of currently selected note for this if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key self.notes_db.set_note_tags(key, evt.value) def observer_view_change_pinned(self, view, evt_type, evt): # get new text and update our database # need local key of currently selected note for this if self.selected_note_idx >= 0: key = self.notes_list_model.list[self.selected_note_idx].key self.notes_db.set_note_pinned(key, evt.value) def observer_view_close(self, view, evt_type, evt): # check that everything has been saved and synced before exiting # first make sure all our queues are up to date self.notes_db.save_threaded() if self.config.simplenote_sync: self.notes_db.sync_to_server_threaded(wait_for_idle=False) syncn = self.notes_db.get_sync_queue_len() wfsn = self.notes_db.waiting_for_simplenote else: syncn = wfsn = 0 # then check all queues saven = self.notes_db.get_save_queue_len() # if there's still something to do, warn the user. if saven or syncn or wfsn: msg = "Are you sure you want to exit? I'm still busy: " + self.helper_save_sync_msg( ) really_want_to_exit = self.view.askyesno("Confirm exit", msg) if really_want_to_exit: self.view.close() else: self.view.close() def observer_view_create_note(self, view, evt_type, evt): # create the note new_key = self.notes_db.create_note(evt.title) # clear the search entry, this should trigger a new list being returned self.view.set_search_entry_text('') # we should focus on our thingy idx = self.notes_list_model.get_idx(new_key) self.view.select_note(idx) def select_note(self, idx): if idx >= 0: key = self.notes_list_model.list[idx].key note = self.notes_db.get_note(key) else: key = None note = None idx = -1 self.selected_note_idx = idx # when we do this, we don't want the change:{text,tags,pinned} events # because those should only fire when they are changed through the UI self.view.mute_note_data_changes() self.view.set_note_data(note) if key: self.view.set_note_status(self.notes_db.get_note_status(key)) self.view.unmute_note_data_changes() def sync_full(self): try: sync_from_server_errors = self.notes_db.sync_full() except SyncError as e: self.view.show_error('Sync error', e.message) else: # regenerate display list # reselect old selection # put cursor where it used to be. self.view.refresh_notes_list() if sync_from_server_errors > 0: self.view.show_error( 'Error syncing notes from server', 'Error syncing %d notes from server. Please check nvpy.log for details.' % (sync_from_server_errors, ))
def __init__(self, config): SubjectMixin.MAIN_THREAD = threading.current_thread() # should probably also look in $HOME self.config = config self.config.app_version = VERSION # configure logging module ############################# # first create db directory if it doesn't exist yet. if not os.path.exists(self.config.db_path): os.mkdir(self.config.db_path) log_filename = os.path.join(self.config.db_path, 'nvpy.log') # file will get nuked when it reaches 100kB lhandler = RotatingFileHandler(log_filename, maxBytes=100000, backupCount=1) lhandler.setLevel(logging.DEBUG) lhandler.setFormatter(logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(message)s')) # we get the root logger and configure it logger = logging.getLogger() if self.config.debug == 1: logger.setLevel(logging.DEBUG) logger.addHandler(lhandler) # this will go to the root logger logging.debug('nvpy logging initialized') logging.debug('config read from %s' % (str(self.config.files_read),)) if self.config.sn_username == '': self.config.simplenote_sync = 0 rst_css = self.config.rest_css_path if rst_css: if rst_css.startswith("~/"): # On Mac, paths that start with '~/' aren't found by path.exists rst_css = rst_css.replace( "~", os.path.abspath(os.path.expanduser('~')), 1) self.config.rest_css_path = rst_css if not os.path.exists(rst_css): # Couldn't find the user-defined css file. Use docutils css instead. self.config.rest_css_path = None md_css = self.config.md_css_path if md_css: if md_css.startswith("~/"): # On Mac, paths that start with '~/' aren't found by path.exists md_css = md_css.replace( "~", os.path.abspath(os.path.expanduser('~')), 1) self.config.md_css_path = md_css if not os.path.exists(md_css): # Couldn't find the user-defined css file. # Do not use css styling for markdown. self.config.md_css_path = None self.notes_list_model = NotesListModel() # create the interface self.view = view.View(self.config, self.notes_list_model) # read our database of notes into memory # and sync with simplenote. try: self.notes_db = NotesDB(self.config) except ReadError as e: emsg = "Please check nvpy.log.\n" + str(e) self.view.show_error('Sync error', emsg) exit(1) self.notes_db.add_observer('synced:note', self.observer_notes_db_synced_note) self.notes_db.add_observer('change:note-status', self.observer_notes_db_change_note_status) if self.config.simplenote_sync: self.notes_db.add_observer('progress:sync_full', self.observer_notes_db_sync_full) self.notes_db.add_observer('error:sync_full', self.observer_notes_db_error_sync_full) self.notes_db.add_observer('complete:sync_full', self.observer_notes_db_complete_sync_full) # we want to be notified when the user does stuff self.view.add_observer('click:notelink', self.observer_view_click_notelink) self.view.add_observer('delete:note', self.observer_view_delete_note) self.view.add_observer('select:note', self.observer_view_select_note) self.view.add_observer('change:entry', self.observer_view_change_entry) self.view.add_observer('change:text', self.observer_view_change_text) self.view.add_observer('change:pinned', self.observer_view_change_pinned) self.view.add_observer('create:note', self.observer_view_create_note) self.view.add_observer('keep:house', self.observer_view_keep_house) self.view.add_observer('command:markdown', self.observer_view_markdown) self.view.add_observer('command:rest', self.observer_view_rest) self.view.add_observer('delete:tag', self.observer_view_delete_tag) self.view.add_observer('add:tag', self.observer_view_add_tag) if self.config.simplenote_sync: self.view.add_observer('command:sync_full', lambda v, et, e: self.sync_full()) self.view.add_observer('command:sync_current_note', self.observer_view_sync_current_note) self.view.add_observer('close', self.observer_view_close) # setup UI to reflect our search mode and case sensitivity self.view.set_cs(self.config.case_sensitive, silent=True) self.view.set_search_mode(self.config.search_mode, silent=True) self.view.add_observer('change:cs', self.observer_view_change_cs) self.view.add_observer('change:search_mode', self.observer_view_change_search_mode) # nn is a list of (key, note) objects nn, match_regexp, active_notes = self.notes_db.filter_notes() # this will trigger the list_change event self.notes_list_model.set_list(nn) self.notes_list_model.match_regexp = match_regexp self.view.set_note_tally(len(nn), active_notes, len(self.notes_db.notes)) # we'll use this to keep track of the currently selected note # we only use idx, because key could change from right under us. self.selected_note_key = None self.view.select_note(0) if self.config.simplenote_sync: self.view.after(0, self.sync_full)
def __init__(self): # setup appdir if hasattr(sys, 'frozen') and sys.frozen: self.appdir, _ = os.path.split(sys.executable) else: dirname = os.path.dirname(__file__) if dirname and dirname != os.curdir: self.appdir = dirname else: self.appdir = os.getcwd() # make sure it's the full path self.appdir = os.path.abspath(self.appdir) # should probably also look in $HOME self.config = Config(self.appdir) self.config.app_version = VERSION # configure logging module ############################# # first create db directory if it doesn't exist yet. if not os.path.exists(self.config.db_path): os.mkdir(self.config.db_path) log_filename = os.path.join(self.config.db_path, 'nvpy.log') # file will get nuked when it reaches 100kB lhandler = RotatingFileHandler(log_filename, maxBytes=100000) lhandler.setLevel(logging.DEBUG) lhandler.setFormatter(logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(message)s')) # we get the root logger and configure it logger = logging.getLogger() logger.setLevel(logging.DEBUG) logger.addHandler(lhandler) # this will go to the root logger logging.debug('nvpy logging initialized') logging.debug('config read from %s' % (str(self.config.files_read),)) if self.config.sn_username == '': self.config.simplenote_sync = 0 css = self.config.rest_css_path if css: if css.startswith("~/"): # On Mac, paths that start with '~/' aren't found by path.exists css = css.replace( "~", os.path.abspath(os.path.expanduser('~')), 1) self.config.rest_css_path = css if not os.path.exists(css): # Couldn't find the user-defined css file. Use docutils css instead. self.config.rest_css_path = None # read our database of notes into memory # and sync with simplenote. self.notes_db = NotesDB(self.config) self.notes_db.add_observer('synced:note', self.observer_notes_db_synced_note) self.notes_db.add_observer('change:note-status', self.observer_notes_db_change_note_status) if self.config.simplenote_sync: self.notes_db.add_observer('progress:sync_full', self.observer_notes_db_sync_full) self.notes_list_model = NotesListModel() # create the interface self.view = view.View(self.config, self.notes_list_model) # we want to be notified when the user does stuff self.view.add_observer('click:notelink', self.observer_view_click_notelink) self.view.add_observer('delete:note', self.observer_view_delete_note) self.view.add_observer('select:note', self.observer_view_select_note) self.view.add_observer('change:entry', self.observer_view_change_entry) self.view.add_observer('change:text', self.observer_view_change_text) self.view.add_observer('change:tags', self.observer_view_change_tags) self.view.add_observer('change:pinned', self.observer_view_change_pinned) self.view.add_observer('create:note', self.observer_view_create_note) self.view.add_observer('keep:house', self.observer_view_keep_house) self.view.add_observer('command:markdown', self.observer_view_markdown) self.view.add_observer('command:rest', self.observer_view_rest) if self.config.simplenote_sync: self.view.add_observer('command:sync_full', lambda v, et, e: self.sync_full()) self.view.add_observer('command:sync_current_note', self.observer_view_sync_current_note) self.view.add_observer('close', self.observer_view_close) # setup UI to reflect our search mode and case sensitivity self.view.set_cs(self.config.case_sensitive, silent=True) self.view.set_search_mode(self.config.search_mode, silent=True) self.view.add_observer('change:cs', self.observer_view_change_cs) self.view.add_observer('change:search_mode', self.observer_view_change_search_mode) # nn is a list of (key, note) objects nn, match_regexp, active_notes = self.notes_db.filter_notes() # this will trigger the list_change event self.notes_list_model.set_list(nn) self.notes_list_model.match_regexp = match_regexp self.view.set_note_tally(len(nn), active_notes, len(self.notes_db.notes)) # we'll use this to keep track of the currently selected note # we only use idx, because key could change from right under us. self.selected_note_idx = -1 self.view.select_note(0) # perform full sync with server, and refresh notes list if successful if self.config.simplenote_sync: self.sync_full()
def __init__(self): self.db = NotesDB()