Work with binary data and catch more exceptions

This commit is contained in:
Tanner Collin 2017-10-13 18:46:22 -06:00
parent be39849693
commit c7309c4136
4 changed files with 51 additions and 19 deletions

11
api.py
View File

@ -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):

View File

@ -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):

View File

@ -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()
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)

View File

@ -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.')
try:
fuse = FUSE(StandardNotesFUSE(sn_api, sync_sec),
args.mountpoint,
foreground=args.foreground,
nothreads=True) # benefits don't outweigh the costs
nothreads=True) # FUSE can't make threads, but we can
except RuntimeError as e:
print('Error mounting file system.')
logging.info('Exiting.')