Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

189 lignes
6.9 KiB

import argparse
from configparser import ConfigParser
from getpass import getpass
import logging
import os
import pathlib
import sys
import appdirs
from fuse import FUSE
from requests.exceptions import ConnectionError, MissingSchema
from api import SNAPIException, StandardNotesAPI
from sn_fuse import StandardNotesFUSE
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: '
''+str(DEFAULT_SYNC_SEC))
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 = {}
login_success = False
# configure logging
if args.verbosity == 1:
log_level = logging.INFO
elif args.verbosity == 2:
log_level = logging.DEBUG
else:
log_level = logging.ERROR
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.gen_keys(password)
del password
sn_api.sign_in(keys)
log_msg = 'Successfully logged into account "%s".'
logging.info(log_msg % username)
login_success = True
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:
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".'
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.')
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.')
if __name__ == '__main__':
main()