From c7309c4136c37bcff6a8abcc95ff046f603566d0 Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Fri, 13 Oct 2017 18:46:22 -0600 Subject: [PATCH] Work with binary data and catch more exceptions --- api.py | 11 +++++++++++ itemmanager.py | 13 ++++++++++--- sn_fuse.py | 12 ++++++++---- standardnotes_fs.py | 34 ++++++++++++++++++++++------------ 4 files changed, 51 insertions(+), 19 deletions(-) diff --git a/api.py b/api.py index a90c7d4..cbdb6d5 100644 --- a/api.py +++ b/api.py @@ -2,6 +2,9 @@ import json, requests, time from crypt import EncryptionHelper +class SNAPIException(Exception): + pass + class RESTAPI: def __init__(self, base_url): self.base_url = base_url @@ -27,11 +30,19 @@ class StandardNotesAPI: def genKeys(self, password): pw_info = self.getAuthParamsForEmail() + + if 'error' in pw_info: + raise SNAPIException(pw_info['error']['message']) + return self.encryption_helper.pure_generatePasswordAndKey(password, pw_info['pw_salt'], pw_info['pw_cost']) def signIn(self, keys): self.keys = keys res = self.api.post('/auth/sign_in', dict(email=self.username, password=self.keys['pw'])) + + if 'error' in res: + raise SNAPIException(res['error']['message']) + self.api.addHeader(dict(Authorization='Bearer ' + res['token'])) def sync(self, dirty_items): diff --git a/itemmanager.py b/itemmanager.py index 0277729..755916b 100644 --- a/itemmanager.py +++ b/itemmanager.py @@ -44,9 +44,16 @@ class ItemManager: for uuid, item in sorted_items: if item['content_type'] == 'Note': note = item['content'] - text = note['text'] + '\n' - count = 0 # used to remove title duplicates + text = note['text'] + # Add a new line so it outputs pretty + if not text.endswith('\n'): + text += '\n'; + + text = text.encode() # convert to binary data + + # remove title duplicates by adding a number to the end + count = 0 while True: title = note['title'] + ('' if not count else ' ' + str(count + 1)) if title in notes: @@ -66,7 +73,7 @@ class ItemManager: def writeNote(self, uuid, text): item = self.items[uuid] - item['content']['text'] = text.strip() + item['content']['text'] = text.decode() # convert back to string item['dirty'] = True def createNote(self, name, time): diff --git a/sn_fuse.py b/sn_fuse.py index 099eb4a..ca88dc7 100644 --- a/sn_fuse.py +++ b/sn_fuse.py @@ -12,6 +12,7 @@ from threading import Thread, Event from fuse import FUSE, FuseOSError, Operations, LoggingMixIn from itemmanager import ItemManager +from requests.exceptions import ConnectionError class StandardNotesFUSE(LoggingMixIn, Operations): def __init__(self, sn_api, sync_sec, path='.'): @@ -51,7 +52,10 @@ class StandardNotesFUSE(LoggingMixIn, Operations): manually_synced = self.run_sync.wait(timeout=self.sync_sec) if not manually_synced: logging.info('Auto-syncing items...') time.sleep(0.1) # fixes race condition of quick create() then write() - self.item_manager.syncItems() + try: + self.item_manager.syncItems() + except ConnectionError: + logging.error('Unable to connect to sync server. Retrying...') def _syncNow(self): self.run_sync.set() @@ -86,7 +90,7 @@ class StandardNotesFUSE(LoggingMixIn, Operations): def read(self, path, size, offset, fh): note, uuid = self._pathToNote(path) - return note['text'][offset : offset + size].encode() + return note['text'][offset : offset + size] def truncate(self, path, length, fh=None): note, uuid = self._pathToNote(path) @@ -97,14 +101,14 @@ class StandardNotesFUSE(LoggingMixIn, Operations): def write(self, path, data, offset, fh): note, uuid = self._pathToNote(path) + text = note['text'][:offset] + data try: - text = note['text'][:offset] + data.decode() + self.item_manager.writeNote(uuid, text) except UnicodeError: logging.error('Unable to parse non-unicode data.') raise FuseOSError(errno.EIO) - self.item_manager.writeNote(uuid, text) self._syncNow() return len(data) diff --git a/standardnotes_fs.py b/standardnotes_fs.py index 28323ea..58337e2 100644 --- a/standardnotes_fs.py +++ b/standardnotes_fs.py @@ -8,9 +8,10 @@ import sys from configparser import ConfigParser from getpass import getpass -from api import StandardNotesAPI +from api import StandardNotesAPI, SNAPIException from sn_fuse import StandardNotesFUSE from fuse import FUSE +from requests.exceptions import ConnectionError, MissingSchema OFFICIAL_SERVER_URL = 'https://sync.standardnotes.org' DEFAULT_SYNC_SEC = 30 @@ -54,6 +55,7 @@ def main(): args = parse_options() config = ConfigParser() keys = {} + login_success = False # configure logging if args.verbosity == 1: @@ -61,7 +63,7 @@ def main(): elif args.verbosity == 2: log_level = logging.DEBUG else: - log_level = logging.CRITICAL + log_level = logging.ERROR logging.basicConfig(level=log_level, format='%(levelname)-8s: %(message)s') if args.verbosity: args.foreground = True @@ -143,18 +145,23 @@ def main(): log_msg = 'Successfully logged into account "%s".' logging.info(log_msg % username) login_success = True - except: - log_msg = 'Failed to log into account "%s".' - print(log_msg % username) - login_success = False + except SNAPIException as e: + print(e) + except ConnectionError: + log_msg = 'Unable to connect to the sync server at "%s".' + print(log_msg % sync_url) + except MissingSchema: + log_msg = 'Invalid sync server url "%s".' + print(log_msg % sync_url) # write settings back if good, clear if not if not args.no_config_file: - config.read_dict(dict(user=dict(sync_url=sync_url, username=username), - keys=keys)) try: with config_file.open(mode='w+') as f: if login_success: + config.read_dict(dict(user=dict(sync_url=sync_url, + username=username), + keys=keys)) config.write(f) log_msg = 'Config written to file "%s".' logging.info(log_msg % str(config_file)) @@ -168,10 +175,13 @@ def main(): if login_success: logging.info('Starting FUSE filesystem.') - fuse = FUSE(StandardNotesFUSE(sn_api, sync_sec), - args.mountpoint, - foreground=args.foreground, - nothreads=True) # benefits don't outweigh the costs + try: + fuse = FUSE(StandardNotesFUSE(sn_api, sync_sec), + args.mountpoint, + foreground=args.foreground, + nothreads=True) # FUSE can't make threads, but we can + except RuntimeError as e: + print('Error mounting file system.') logging.info('Exiting.')