You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

179 lines
6.5 KiB

import appdirs
import argparse
import logging
import os
import pathlib
import sys
from configparser import ConfigParser
from getpass import getpass
from api import StandardNotesAPI
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
cfg_env = os.environ.get('SN_FS_CONFIG_PATH')
CONFIG_PATH = cfg_env if cfg_env else appdirs.user_config_dir(APP_NAME)
CONFIG_FILE = pathlib.PurePath(CONFIG_PATH, APP_NAME + '.conf')
def parse_options():
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('mountpoint', nargs='?', help='local mountpoint folder')
parser.add_argument('--username',
help='Standard Notes username to log in with')
parser.add_argument('--password',
help='Standard Notes password to log in with\n'
'NOTE: It is NOT recommended to use this option!\n'
' The password may be stored in history, so\n'
' use the password prompt instead.')
parser.add_argument('-v', '--verbosity', action='count',
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', default=CONFIG_FILE,
help='specify a config file location. Defaults to:\n'
''+str(CONFIG_FILE))
parser.add_argument('--logout', action='store_true',
help='delete login credentials saved in config and quit')
return parser.parse_args()
def main():
args = parse_options()
config = ConfigParser()
keys = {}
# configure logging
if args.verbosity == 1:
log_level = logging.INFO
elif args.verbosity == 2:
log_level = logging.DEBUG
else:
log_level = logging.CRITICAL
logging.basicConfig(level=log_level,
format='%(levelname)-8s: %(message)s')
if args.verbosity: args.foreground = True
# figure out config file
config_file = pathlib.Path(args.config)
# logout and quit if wanted
if args.logout:
try:
config_file.unlink()
print('Config file deleted and logged out.')
except OSError:
logging.info('Already logged out.')
sys.exit(0)
# make sure mountpoint is specified
if not args.mountpoint:
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:
config_file.parent.mkdir(mode=0o0700, parents=True, exist_ok=True)
log_msg = 'Using config directory "%s".'
logging.info(log_msg % str(config_file.parent))
except OSError:
log_msg = 'Error creating config file directory "%s".'
print(log_msg % str(config_file.parent))
sys.exit(1)
try:
with config_file.open() as f:
config.read_file(f)
log_msg = 'Loaded config file "%s".'
logging.info(log_msg % str(config_file))
except OSError:
log_msg = 'Unable to read config file "%s".'
logging.info(log_msg % str(config_file))
# figure out all login params
if args.sync_url:
sync_url = args.sync_url
elif config.has_option('user', 'sync_url'):
sync_url = config.get('user', 'sync_url')
else:
sync_url = OFFICIAL_SERVER_URL
log_msg = 'Using sync URL "%s".'
logging.info(log_msg % sync_url)
if config.has_option('user', 'username') \
and config.has_section('keys') \
and not args.username \
and not args.password:
username = config.get('user', 'username')
keys = dict(config.items('keys'))
else:
username = args.username if args.username else \
input('Please enter your Standard Notes username: ')
password = args.password if args.password else \
getpass('Please enter your password (hidden): ')
# log the user in
try:
sn_api = StandardNotesAPI(sync_url, username)
if not keys:
keys = sn_api.genKeys(password)
del password
sn_api.signIn(keys)
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
# 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.write(f)
log_msg = 'Config written to file "%s".'
logging.info(log_msg % str(config_file))
else:
log_msg = 'Clearing config file "%s".'
logging.info(log_msg % config_file)
config_file.chmod(0o600)
except OSError:
log_msg = 'Unable to write config file "%s".'
logging.warning(log_msg % str(config_file))
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
logging.info('Exiting.')
if __name__ == '__main__':
main()