Work with binary data and catch more exceptions
This commit is contained in:
parent
be39849693
commit
c7309c4136
11
api.py
11
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):
|
||||
|
|
|
@ -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):
|
||||
|
|
10
sn_fuse.py
10
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()
|
||||
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)
|
||||
|
||||
|
|
|
@ -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.')
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user