commit 83a8c9d9190ff889732856cb1e76f97eb2daf288
parent cc241e583d345ed364a46bb57f677fdf028fd660
Author: Daniel Moch <daniel@danielmoch.com>
Date: Fri, 27 Jul 2018 12:41:00 -0400
Initial refactor to use NextCloud Notes API
Still many broken areas
Diffstat:
7 files changed, 78 insertions(+), 196 deletions(-)
diff --git a/README.md b/README.md
@@ -216,7 +216,7 @@ example:
``` echo '{"tags":["testing","new"],"content":"New note!"}' | nncli
import - ```
-Allowed fields are `content`, `tags`, `systemtags`, `modifydate`,
+Allowed fields are `content`, `tags`, `systemtags`, `modified`,
`createdate`, and `deleted`.
### Exporting
diff --git a/nnotes_cli/nncli.py b/nnotes_cli/nncli.py
@@ -984,7 +984,7 @@ def gui(self, key):
view_note.ViewNote(self.config,
{
'ndb' : self.ndb,
- 'key' : key, # initial key to view or None
+ 'id' : key, # initial key to view or None
'log' : self.log
})
@@ -1098,7 +1098,7 @@ def cli_note_dump(self, key):
w = 60
sep = '+' + '-'*(w+2) + '+'
- t = time.localtime(float(note['modifydate']))
+ t = time.localtime(float(note['modified']))
mod_time = time.strftime('%a, %d %b %Y %H:%M:%S', t)
title = utils.get_note_title(note)
flags = utils.get_note_flags(note)
@@ -1106,7 +1106,7 @@ def cli_note_dump(self, key):
print(sep)
print(('| {:<' + str(w) + '} |').format((' Title: ' + title)[:w]))
- print(('| {:<' + str(w) + '} |').format((' Key: ' + note.get('key', 'Localkey: {}'.format(note.get('localkey'))))[:w]))
+ print(('| {:<' + str(w) + '} |').format((' Key: ' + note.get('id', 'Localkey: {}'.format(note.get('localkey'))))[:w]))
print(('| {:<' + str(w) + '} |').format((' Date: ' + mod_time)[:w]))
print(('| {:<' + str(w) + '} |').format((' Tags: ' + tags)[:w]))
print(('| {:<' + str(w) + '} |').format((' Version: v' + str(note.get('version', 0)))[:w]))
diff --git a/nnotes_cli/nnotes.py b/nnotes_cli/nnotes.py
@@ -66,64 +66,16 @@ def __init__(self, username, password, host):
""" object constructor """
self.username = urllib.parse.quote(username)
self.password = urllib.parse.quote(password)
- self.AUTH_URL = 'https://{0}/api/login'.format(host)
- self.DATA_URL = 'https://{0}/api2/data'.format(host)
- self.INDX_URL = 'https://{0}/api2/index?'.format(host)
- self.token = None
+ self.api_url = \
+ 'https://{}:{}@{}/index.php/apps/notes/api/v0.2/notes'. \
+ format(username, password, host)
self.status = 'offline'
- def authenticate(self, user, password):
- """ Method to get NextCloud Notes auth token
-
- Arguments:
- - user (string): NextCloud username
- - password (string): NextCloud password
-
- Returns:
- NextCloud API token as string
-
- """
- auth_params = "email=%s&password=%s" % (user, password)
- values = base64.encodestring(auth_params.encode())
- try:
- res = requests.post(self.AUTH_URL, data=values)
- token = res.text
- if res.status_code != 200:
- self.status = 'login failed with status {}, check credentials'.format(res.status_code)
- token = None
- else:
- self.status = 'online'
- except ConnectionError as e:
- token = None
- self.status = 'offline, connection error'
- except RequestException as e:
- token = None
- self.status = 'login failed, check log'
-
- logging.debug('AUTHENTICATE: ' + self.status)
- return token
-
- def get_token(self):
- """ Method to retrieve an auth token.
-
- The cached global token is looked up and returned if it exists. If it
- is `None` a new one is requested and returned.
-
- Returns:
- NextCloud API token as string
-
- """
- if self.token is None:
- self.token = self.authenticate(self.username, self.password)
- return self.token
-
-
- def get_note(self, noteid, version=None):
+ def get_note(self, noteid):
""" method to get a specific note
Arguments:
- noteid (string): ID of the note to get
- - version (int): optional version of the note to get
Returns:
A tuple `(note, status)`
@@ -133,16 +85,10 @@ def get_note(self, noteid, version=None):
"""
# request note
- params_version = ""
- if version is not None:
- params_version = '/' + str(version)
-
- params = {'auth': self.get_token(),
- 'email': self.username }
- url = '{}/{}{}'.format(self.DATA_URL, str(noteid), params_version)
+ url = '{}/{}'.format(self.api_url, str(noteid))
#logging.debug('REQUEST: ' + self.DATA_URL+params)
try:
- res = requests.get(url, params=params)
+ res = requests.get(url)
res.raise_for_status()
note = res.json()
except ConnectionError as e:
@@ -163,8 +109,8 @@ def get_note(self, noteid, version=None):
return note, 0
def update_note(self, note):
- """ function to update a specific note object, if the note object does not
- have a "key" field, a new note is created
+ """ function to update a specific note object, if the note
+ object does not have a "key" field, a new note is created
Arguments
- note (dict): note object to update
@@ -182,19 +128,22 @@ def update_note(self, note):
# determine whether to create a new note or updated an existing one
params = {'auth': self.get_token(),
'email': self.username}
- if "key" in note:
+ if "id" in note:
# set modification timestamp if not set by client
- if 'modifydate' not in note:
- note["modifydate"] = time.time()
+ if 'modified' not in note:
+ note["modified"] = time.time()
- url = '%s/%s' % (self.DATA_URL, note["key"])
+ url = '{}/{}'.format(self.api_url, note["id"])
else:
- url = self.DATA_URL
+ url = self.api_url
#logging.debug('REQUEST: ' + url + ' - ' + str(note))
try:
data = urllib.parse.quote(json.dumps(note))
- res = requests.post(url, data=data, params=params)
+ if "id" in note:
+ res = requests.put(url, data=data)
+ else:
+ res = requests.post(url, data=data)
res.raise_for_status()
note = res.json()
except ConnectionError as e:
@@ -234,7 +183,7 @@ def add_note(self, note):
else:
return "No string or valid note.", -1
- def get_note_list(self, since=None, tags=[]):
+ def get_note_list(self, category=None):
""" function to get the note list
The function can be passed optional arguments to limit the
@@ -243,40 +192,32 @@ def get_note_list(self, since=None, tags=[]):
is returned.
Arguments:
- - since=time.time() epoch stamp: only return notes modified
- since this date
- - tags=[] list of tags as string: return notes that have
+ - category=None list of tags as string: return notes that have
at least one of these tags
Returns:
A tuple `(notes, status)`
- - notes (list): A list of note objects with all properties set except
- `content`.
+ - notes (list): A list of note objects with all properties
+ set except `content`.
- status (int): 0 on sucesss and -1 otherwise
"""
# initialize data
status = 0
- notes = { "data" : [] }
- json_data = {}
+ note_list = {}
# get the note index
- params = {'auth': self.get_token(),
- 'email': self.username,
- 'length': NOTE_FETCH_LENGTH
- }
- if since is not None:
- params['since'] = since
+ params = {'exclude': 'content'}
# perform initial HTTP request
try:
- #logging.debug('REQUEST: ' + self.INDX_URL+params)
- res = requests.get(self.INDX_URL, params=params)
+ logging.debug('REQUEST: ' + self.api_url + \
+ '?exclude=content')
+ res = requests.get(self.api_url, params=params)
res.raise_for_status()
#logging.debug('RESPONSE OK: ' + str(res))
- json_data = res.json()
- notes["data"].extend(json_data["data"])
+ note_list = res.json()
except ConnectionError as e:
self.status = 'offline, connection error'
status = -1
@@ -287,66 +228,15 @@ def get_note_list(self, since=None, tags=[]):
# if invalid json data
status = -1
- # get additional notes if bookmark was set in response
- while "mark" in json_data:
- params = {'auth': self.get_token(),
- 'email': self.username,
- 'mark': json_data['mark'],
- 'length': NOTE_FETCH_LENGTH
- }
- if since is not None:
- params['since'] = since
-
- # perform the actual HTTP request
- try:
- #logging.debug('REQUEST: ' + self.INDX_URL+params)
- res = requests.get(self.INDX_URL, params=params)
- res.raise_for_status()
- json_data = res.json()
- #logging.debug('RESPONSE OK: ' + str(res))
- notes["data"].extend(json_data["data"])
- except ConnectionError as e:
- self.status = 'offline, connection error'
- status = -1
- except RequestException as e:
- # if problem with network request/response
- status = -1
- except ValueError as e:
- # if invalid json data
- status = -1
-
- # parse data fields in response
- note_list = notes["data"]
-
- # Can only filter for tags at end, once all notes have been retrieved.
- #Below based on simplenote.vim, except we return deleted notes as well
- if (len(tags) > 0):
- note_list = [n for n in note_list if (len(set(n["tags"]).intersection(tags)) > 0)]
+ # Can only filter for category at end, once all notes have been
+ # retrieved. Below based on simplenote.vim, except we return
+ # deleted notes as well
+ if category is not None:
+ note_list = \
+ [n for n in note_list if n["category"] == category]
return note_list, status
- def trash_note(self, note_id):
- """ method to move a note to the trash
-
- Arguments:
- - note_id (string): key of the note to trash
-
- Returns:
- A tuple `(note, status)`
-
- - note (dict): the newly created note or an error message
- - status (int): 0 on sucesss and -1 otherwise
-
- """
- # get note
- note, status = self.get_note(note_id)
- if (status == -1):
- return note, status
- # set deleted property
- note["deleted"] = 1
- # update note
- return self.update_note(note)
-
def delete_note(self, note_id):
""" method to permanently delete a note
@@ -361,17 +251,11 @@ def delete_note(self, note_id):
"""
# notes have to be trashed before deletion
- note, status = self.trash_note(note_id)
- if (status == -1):
- return note, status
-
- params = {'auth': self.get_token(),
- 'email': self.username }
- url = '{}/{}'.format(self.DATA_URL, str(note_id))
+ url = '{}/{}'.format(self.api_url, str(note_id))
try:
#logging.debug('REQUEST DELETE: ' + self.DATA_URL+params)
- res = requests.delete(url, params=params)
+ res = requests.delete(url)
res.raise_for_status()
except ConnectionError as e:
self.status = 'offline, connection error'
@@ -379,4 +263,3 @@ def delete_note(self, note_id):
except RequestException as e:
return e, -1
return {}, 0
-
diff --git a/nnotes_cli/notes_db.py b/nnotes_cli/notes_db.py
@@ -1,4 +1,3 @@
-
#
# The MIT License (MIT)
#
@@ -73,14 +72,15 @@ def __init__(self, config, log, update_view):
except ValueError as e:
raise ReadError ('Error reading {0}: {1}'.format(fn, str(e)))
else:
- # we always have a localkey, also when we don't have a note['key'] yet (no sync)
+ # we always have a localkey, also when we don't have a
+ # note['id'] yet (no sync)
localkey = n.get('localkey', os.path.splitext(os.path.basename(fn))[0])
# we maintain in memory a timestamp of the last save
# these notes have just been read, so at this moment
# they're in sync with the disc.
n['savedate'] = now
# set a localkey to each note in memory
- # Note: 'key' is used only for syncing with server - 'localkey'
+ # Note: 'id' is used only for syncing with server - 'localkey'
# is used for everything else in nncli
n['localkey'] = localkey
@@ -103,7 +103,7 @@ def filtered_notes_sort(self, filtered_notes, sort_mode='date'):
if self.config.get_config('pinned_ontop') == 'yes':
filtered_notes.sort(key=utils.sort_by_modify_date_pinned, reverse=True)
else:
- filtered_notes.sort(key=lambda o: -float(o.note.get('modifydate', 0)))
+ filtered_notes.sort(key=lambda o: -float(o.note.get('modified', 0)))
elif sort_mode == 'alpha':
if self.config.get_config('pinned_ontop') == 'yes':
filtered_notes.sort(key=utils.sort_by_title_pinned)
@@ -298,28 +298,27 @@ def filter_notes_regex(self, search_string=None):
def import_note(self, note):
# need to get a key unique to this database. not really important
# what it is, as long as it's unique.
- new_key = note['key'] if note.get('key') else utils.generate_random_key()
+ new_key = note['id'] if note.get('id') else utils.generate_random_key()
while new_key in self.notes:
new_key = utils.generate_random_key()
timestamp = time.time()
try:
- modifydate = float(note.get('modifydate', timestamp))
+ modified = float(note.get('modified', timestamp))
createdate = float(note.get('createdate', timestamp))
except ValueError:
raise ValueError('date fields must be numbers or string representations of numbers')
# note has no internal key yet.
new_note = {
- 'content' : note.get('content', ''),
- 'deleted' : note.get('deleted', 0),
- 'modifydate' : modifydate,
- 'createdate' : createdate,
+ 'content' : note.get('content', ''),
+ 'modified' : modified,
+ 'title' : note.get('title'),
+ 'category' : note.get('category', None),
'savedate' : 0, # never been written to disc
'syncdate' : 0, # never been synced with server
- 'tags' : note.get('tags', []),
- 'systemtags' : note.get('systemtags', [])
+ 'favorite' : False
}
# sanity check all note values
@@ -328,7 +327,7 @@ def import_note(self, note):
if not new_note['deleted'] in (0, 1):
raise ValueError('"deleted" must be 0 or 1')
- for n in (new_note['modifydate'], new_note['createdate']):
+ for n in (new_note['modified']):
if not 0 <= n <= timestamp:
raise ValueError('date fields must be real')
@@ -359,14 +358,14 @@ def create_note(self, content):
# note has no internal key yet.
new_note = {
- 'localkey' : new_key,
- 'content' : content,
- 'deleted' : 0,
- 'modifydate' : timestamp,
- 'createdate' : timestamp,
+ 'localkey' : new_key,
+ 'content' : note.get('content', ''),
+ 'modified' : modified,
+ 'title' : note.get('title'),
+ 'category' : note.get('category', None),
'savedate' : 0, # never been written to disc
'syncdate' : 0, # never been synced with server
- 'tags' : []
+ 'favorite' : False
}
self.notes[new_key] = new_note
@@ -396,7 +395,7 @@ def set_note_deleted(self, key, deleted):
if (not n['deleted'] and deleted) or \
(n['deleted'] and not deleted):
n['deleted'] = deleted
- n['modifydate'] = time.time()
+ n['modified'] = time.time()
self.flag_what_changed(n, 'deleted')
self.log('Note {0} (key={1})'.format('trashed' if deleted else 'untrashed', key))
@@ -405,7 +404,7 @@ def set_note_content(self, key, content):
old_content = n.get('content')
if content != old_content:
n['content'] = content
- n['modifydate'] = time.time()
+ n['modified'] = time.time()
self.flag_what_changed(n, 'content')
self.log('Note content updated (key={0})'.format(key))
@@ -415,7 +414,7 @@ def set_note_tags(self, key, tags):
tags = utils.sanitise_tags(tags)
if tags != old_tags:
n['tags'] = tags
- n['modifydate'] = time.time()
+ n['modified'] = time.time()
self.flag_what_changed(n, 'tags')
self.log('Note tags updated (key={0})'.format(key))
@@ -430,7 +429,7 @@ def set_note_pinned(self, key, pinned):
systemtags.append('pinned')
else:
systemtags.remove('pinned')
- n['modifydate'] = time.time()
+ n['modified'] = time.time()
self.flag_what_changed(n, 'systemtags')
self.log('Note {0} (key={1})'.format('pinned' if pinned else 'unpinned', key))
@@ -445,12 +444,12 @@ def set_note_markdown(self, key, markdown):
systemtags.append('markdown')
else:
systemtags.remove('markdown')
- n['modifydate'] = time.time()
+ n['modified'] = time.time()
self.flag_what_changed(n, 'systemtags')
self.log('Note markdown {0} (key={1})'.format('flagged' if markdown else 'unflagged', key))
def helper_key_to_fname(self, k):
- return os.path.join(self.config.get_config('db_path'), k) + '.json'
+ return os.path.join(self.config.get_config('db_path'), str(k)) + '.json'
def helper_save_note(self, k, note):
# Save a single note to disc.
@@ -498,11 +497,11 @@ def sync_notes(self, server_sync=True, full_sync=True):
for note_index, local_key in enumerate(self.notes.keys()):
n = self.notes[local_key]
- if not n.get('key') or \
- float(n.get('modifydate')) > float(n.get('syncdate')):
+ if not n.get('id') or \
+ float(n.get('modified')) > float(n.get('syncdate')):
savedate = float(n.get('savedate'))
- if float(n.get('modifydate')) > savedate or \
+ if float(n.get('modified')) > savedate or \
float(n.get('syncdate')) > savedate:
# this will trigger a save to disk after sync algorithm
# we want this note saved even if offline or sync fails
@@ -545,7 +544,7 @@ def sync_notes(self, server_sync=True, full_sync=True):
# merge the note we got back (content could be empty)
# record syncdate and save the note at the assigned key
del self.notes[local_key]
- k = uret[0].get('key')
+ k = uret[0].get('id')
n.update(uret[0])
n['syncdate'] = now
n['localkey'] = k
@@ -567,7 +566,7 @@ def sync_notes(self, server_sync=True, full_sync=True):
if not server_sync:
nl = []
else:
- nl = self.note.get_note_list(since=None if full_sync else self.last_sync)
+ nl = self.note.get_note_list()
if nl[1] == 0: # success
nl = nl[0]
@@ -584,7 +583,7 @@ def sync_notes(self, server_sync=True, full_sync=True):
if not skip_remote_syncing:
len_nl = len(nl)
for note_index, n in enumerate(nl):
- k = n.get('key')
+ k = n.get('id')
server_keys[k] = True
# this works because in the prior step we rewrite local keys to
# server keys when we get an updated note back from the server
@@ -660,16 +659,16 @@ def get_note_version(self, key, version):
def get_note_status(self, key):
n = self.notes[key]
o = utils.KeyValueObject(saved=False, synced=False, modified=False)
- modifydate = float(n['modifydate'])
+ modified = float(n['modified'])
savedate = float(n['savedate'])
syncdate = float(n['syncdate'])
- if savedate > modifydate:
+ if savedate > modified:
o.saved = True
else:
o.modified = True
- if syncdate > modifydate:
+ if syncdate > modified:
o.synced = True
return o
diff --git a/nnotes_cli/utils.py b/nnotes_cli/utils.py
@@ -59,7 +59,7 @@ def get_note_tags(note):
# 'm' - markdown
def get_note_flags(note):
flags = ''
- flags += 'X' if float(note['modifydate']) > float(note['syncdate']) else ' '
+ flags += 'X' if float(note['modified']) > float(note['syncdate']) else ' '
flags += 'T' if 'deleted' in note and note['deleted'] else ' '
if 'systemtags' in note:
flags += '*' if 'pinned' in note['systemtags'] else ' '
@@ -180,9 +180,9 @@ def sort_notes_by_tags(notes, pinned_ontop=False):
def sort_by_modify_date_pinned(a):
if note_pinned(a.note):
- return 100.0 * float(a.note.get('modifydate', 0))
+ return 100.0 * float(a.note.get('modified', 0))
else:
- return float(a.note.get('modifydate', 0))
+ return float(a.note.get('modified', 0))
class KeyValueObject:
"""Store key=value pairs in this object and retrieve with o.key.
diff --git a/nnotes_cli/view_note.py b/nnotes_cli/view_note.py
@@ -13,7 +13,7 @@ class ViewNote(urwid.ListBox):
def __init__(self, config, args):
self.config = config
self.ndb = args['ndb']
- self.key = args['key']
+ self.key = args['id']
self.log = args['log']
self.search_string = ''
self.search_mode = 'gstyle'
@@ -136,7 +136,7 @@ def get_status_bar(self):
title = utils.get_note_title(self.old_note)
version = self.old_note['version']
else:
- t = time.localtime(float(self.note['modifydate']))
+ t = time.localtime(float(self.note['modified']))
title = utils.get_note_title(self.note)
flags = utils.get_note_flags(self.note)
tags = utils.get_note_tags(self.note)
diff --git a/nnotes_cli/view_titles.py b/nnotes_cli/view_titles.py
@@ -46,7 +46,7 @@ def format_title(self, note):
%N -- note title
"""
- t = time.localtime(float(note['modifydate']))
+ t = time.localtime(float(note['modified']))
mod_time = time.strftime(self.config.get_config('format_strftime'), t)
title = utils.get_note_title(note)
flags = utils.get_note_flags(note)