Sync notes automatically and on changes

This commit is contained in:
Tanner Collin 2017-10-11 21:00:04 -06:00
parent 2fe82ddf6f
commit efb5f3b8c5
3 changed files with 51 additions and 18 deletions

View File

@ -56,20 +56,18 @@ class ItemManager:
notes[title] = dict(text=text,
created=item['created_at'],
modified=item['updated_at'],
modified=item.get('updated_at', item['created_at']),
uuid=item['uuid'])
return notes
def touchNote(self, uuid):
item = self.items[uuid]
item['dirty'] = True
self.syncItems()
def writeNote(self, uuid, text):
item = self.items[uuid]
item['content']['text'] = text.strip()
item['dirty'] = True
self.syncItems()
def createNote(self, name, time):
uuid = str(uuid1())
@ -82,19 +80,16 @@ class ItemManager:
updated_at=time,
enc_item_key='',
content=content)
self.syncItems()
def renameNote(self, uuid, new_note_name):
item = self.items[uuid]
item['content']['title'] = new_note_name
item['dirty'] = True
self.syncItems()
def deleteNote(self, uuid):
item = self.items[uuid]
item['deleted'] = True
item['dirty'] = True
self.syncItems()
def __init__(self, sn_api):
self.sn_api = sn_api

View File

@ -1,20 +1,20 @@
from __future__ import print_function, absolute_import, division
import errno
import iso8601
import logging
import os
import time
from stat import S_IFDIR, S_IFREG
from sys import argv, exit
from datetime import datetime
from pathlib import PurePath
from threading import Thread, Event
from fuse import FUSE, FuseOSError, Operations, LoggingMixIn
from itemmanager import ItemManager
class StandardNotesFUSE(LoggingMixIn, Operations):
def __init__(self, sn_api, path='.'):
def __init__(self, sn_api, sync_sec, path='.'):
self.item_manager = ItemManager(sn_api)
self.notes = self.item_manager.getNotes()
@ -30,15 +30,38 @@ class StandardNotesFUSE(LoggingMixIn, Operations):
st_mtime=now, st_atime=now, st_nlink=1,
st_uid=self.uid, st_gid=self.gid)
self.sync_sec = sync_sec
self.run_sync = Event()
self.stop_sync = Event()
self.sync_thread = Thread(target=self._syncThread)
self.sync_thread.start()
def destroy(self, path):
self._syncNow()
logging.info('Stopping sync thread.')
self.stop_sync.set()
self.sync_thread.join()
return 0
def _syncThread(self):
while not self.stop_sync.is_set():
self.run_sync.clear()
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()
def _syncNow(self):
self.run_sync.set()
def _pathToNote(self, path):
pp = PurePath(path)
note_name = pp.parts[1]
self.notes = self.item_manager.getNotes()
note = self.notes[note_name]
return note, note['uuid']
def getattr(self, path, fh=None):
self.notes = self.item_manager.getNotes()
if path == '/':
return self.dir_stat
@ -67,6 +90,8 @@ class StandardNotesFUSE(LoggingMixIn, Operations):
note, uuid = self._pathToNote(path)
text = note['text'][:length]
self.item_manager.writeNote(uuid, text)
self._syncNow()
return 0
def write(self, path, data, offset, fh):
note, uuid = self._pathToNote(path)
@ -78,6 +103,7 @@ class StandardNotesFUSE(LoggingMixIn, Operations):
raise FuseOSError(errno.EIO)
self.item_manager.writeNote(uuid, text)
self._syncNow()
return len(data)
def create(self, path, mode):
@ -92,11 +118,13 @@ class StandardNotesFUSE(LoggingMixIn, Operations):
now = datetime.utcnow().isoformat()[:-3] + 'Z' # hack
self.item_manager.createNote(note_name, now)
self._syncNow()
return 0
def unlink(self, path):
note, uuid = self._pathToNote(path)
self.item_manager.deleteNote(uuid)
self._syncNow()
return 0
def mkdir(self, path, mode):
@ -106,6 +134,7 @@ class StandardNotesFUSE(LoggingMixIn, Operations):
def utimens(self, path, times=None):
note, uuid = self._pathToNote(path)
self.item_manager.touchNote(uuid)
self._syncNow()
return 0
def rename(self, old, new):
@ -113,6 +142,7 @@ class StandardNotesFUSE(LoggingMixIn, Operations):
new_path_parts = new.split('/')
new_note_name = new_path_parts[1]
self.item_manager.renameNote(uuid, new_note_name)
self._syncNow()
return 0
def chmod(self, path, mode):
@ -123,9 +153,6 @@ class StandardNotesFUSE(LoggingMixIn, Operations):
logging.error('chown is disabled.')
raise FuseOSError(errno.EPERM)
def destroy(self, path):
return 0
def readlink(self, path):
return 0

View File

@ -13,6 +13,8 @@ from sn_fuse import StandardNotesFUSE
from fuse import FUSE
OFFICIAL_SERVER_URL = 'https://sync.standardnotes.org'
DEFAULT_SYNC_SEC = 30
MINIMUM_SYNC_SEC = 5
APP_NAME = 'standardnotes-fs'
# path settings
@ -34,12 +36,14 @@ def parse_options():
help='output verbosity -v or -vv (implies --foreground)')
parser.add_argument('--foreground', action='store_true',
help='run standardnotes-fs in the foreground')
parser.add_argument('--sync-sec', type=int, default=DEFAULT_SYNC_SEC,
help='how many seconds between each sync. Default: 10')
parser.add_argument('--sync-url',
help='URL of Standard File sync server. Defaults to:\n'
''+OFFICIAL_SERVER_URL)
parser.add_argument('--no-config-file', action='store_true',
help='don\'t load or create a config file')
parser.add_argument('--config',
parser.add_argument('--config', default=CONFIG_FILE,
help='specify a config file location. Defaults to:\n'
''+str(CONFIG_FILE))
parser.add_argument('--logout', action='store_true',
@ -63,8 +67,7 @@ def main():
if args.verbosity: args.foreground = True
# figure out config file
config_file = args.config if args.config else CONFIG_FILE
config_file = pathlib.Path(config_file)
config_file = pathlib.Path(args.config)
# logout and quit if wanted
if args.logout:
@ -80,6 +83,14 @@ def main():
print('No mountpoint specified.')
sys.exit(1)
# keep sync_sec above the minimum sync time
if args.sync_sec < MINIMUM_SYNC_SEC:
sync_sec = MINIMUM_SYNC_SEC
print('Sync interval must be at least', MINIMUM_SYNC_SEC,
'seconds. Using that instead.')
else:
sync_sec = args.sync_sec
# load config file settings
if not args.no_config_file:
try:
@ -157,7 +168,7 @@ def main():
if login_success:
logging.info('Starting FUSE filesystem.')
fuse = FUSE(StandardNotesFUSE(sn_api),
fuse = FUSE(StandardNotesFUSE(sn_api, sync_sec),
args.mountpoint,
foreground=args.foreground,
nothreads=True) # benefits don't outweigh the costs