Beispiel #1
0
    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)
Beispiel #2
0
Datei: nvpy.py Projekt: mt3/nvpy
    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()
Beispiel #3
0
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])
Beispiel #4
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)
Beispiel #5
0
Datei: nvpy.py Projekt: mt3/nvpy
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()
Beispiel #6
0
    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()
Beispiel #7
0
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,))
Beispiel #8
0
    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()
Beispiel #9
0
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()
Beispiel #10
0
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, ))
Beispiel #11
0
    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)
Beispiel #12
0
    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()
Beispiel #13
0
 def __init__(self):
     self.db = NotesDB()