Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 115 additions & 9 deletions geeknote/gnsync.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import logging
import string
import unicodedata, re
import pickle

from geeknote import GeekNote
from storage import Storage
Expand All @@ -19,6 +20,9 @@
handler = logging.FileHandler(def_logpath)
handler.setFormatter(formatter)

mtime_file_extention = '.mtime'
rej_file_extension = '.rej'

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger.addHandler(handler)
Expand Down Expand Up @@ -80,12 +84,14 @@ class GNSync:
path = None
mask = None
twoway = None
merged = None

notebook_guid = None
all_set = False
mtime_file = None

@log
def __init__(self, notebook_name, path, mask, format, twoway=False):
def __init__(self, notebook_name, path, mask, format, twoway=False, merged=False):
# check auth
if not Storage().getUserToken():
raise Exception("Auth error. There is not any oAuthToken.")
Expand All @@ -98,6 +104,9 @@ def __init__(self, notebook_name, path, mask, format, twoway=False):
raise Exception("Path to sync directories does not exist.")

self.path = path
self.mtime_file = os.path.join(self.path, '.' + notebook_name + mtime_file_extention)
if not os.path.exists(self.mtime_file):
open(self.mtime_file, 'wb') # create the mtime file

#set mask
if not mask:
Expand All @@ -117,6 +126,7 @@ def __init__(self, notebook_name, path, mask, format, twoway=False):
self.extension = ".txt"

self.twoway = twoway
self.merged = merged

logger.info('Sync Start')

Expand All @@ -138,30 +148,95 @@ def sync(self):
files = self._get_files()
notes = self._get_notes()

mtimes = self._get_mtimes()
if mtimes is None:
mtimes = {}

#track files and notes changed in this sync.
updated = []

for f in files:
has_note = False
for n in notes:
if f['name'] == n.title:
has_note = True
if f['mtime'] > n.updated:
self._update_note(f, n)
break
#get saved mtime for the note
mtime = mtimes.get(n.title, 0)
if mtime != 0:
if mtime['file_mtime'] == f['mtime'] and \
mtime['note_mtime'] == n.updated:
#both have not changed since last sync
break
elif mtime['file_mtime'] != f['mtime'] and \
mtime['note_mtime'] != n.updated:
#both changed, need merge
if self.merged:
self._update_note(f, n)
updated.append(f['name'])
else:
logger.warning('Skipped note (CONFLICT!!!): {0},\n'
'merge the note manually and sync the '
'notebook again with --merged option'.format(f['name']))
self._create_rej_file(n)
break
elif mtime['file_mtime'] != f['mtime']:
#only local note changed
self._update_note(f, n)
updated.append(f['name'])
else:
#only server note changed, handle it in twoway mode
pass
else:
if f['mtime'] > n.updated:
self._update_note(f, n)
updated.append(f['name'])

break

if not has_note:
self._create_note(f)
updated.append(f['name'])

if self.twoway:
for n in notes:
has_file = False
for f in files:
if f['name'] == n.title:
has_file = True
if f['mtime'] < n.updated:
self._update_file(f, n)
has_file = True
mtime = mtimes.get(n.title, 0)
if mtime != 0:
if mtime['file_mtime'] == f['mtime'] and \
mtime['note_mtime'] == n.updated:
break
elif mtime['file_mtime'] != f['mtime'] and \
mtime['note_mtime'] != n.updated:
break #handled already
elif mtime['note_mtime'] != n.updated:
#only server note changed
self._update_file(f, n)
updated.append(n.title)
else:
pass #handled already
else:
if f['mtime'] < n.updated:
self._update_file(f, n)
updated.append(n.title)

break

if not has_file:
self._create_file(n)
updated.append(n.title)

# after sync, save the mtimes of both files and notes
files = self._get_files()
notes = self._get_notes()
for f in files:
for n in notes:
if f['name'] == n.title and n.title in updated:
mtimes[n.title] = {'file_mtime':f['mtime'], 'note_mtime':n.updated}

self._save_mtimes(mtimes)

logger.info('Sync Complete')

Expand Down Expand Up @@ -230,6 +305,19 @@ def _create_file(self, note):
open(path, "w").write(content)
return True

@log
def _create_rej_file(self, note):
"""
Create reject file for note
"""
GeekNote().loadNoteContent(note)
content = Editor.ENMLtoText(note.content)
rej_file = "." + note.title + self.extension + rej_file_extension
path = os.path.join(self.path, rej_file)
open(path, "w").write(content)
logger.info('Created reject file {0} for note: {1}'.format(rej_file, note.title))
return True

@log
def _get_file_content(self, path):
"""
Expand All @@ -240,7 +328,7 @@ def _get_file_content(self, path):
# strip unprintable characters
content = remove_control_characters(content.decode('utf-8')).encode('utf-8')
content = Editor.textToENML(content=content, raise_ex=True, format=self.format)

if content is None:
logger.warning("File {0}. Content must be " \
"an UTF-8 encode.".format(path))
Expand Down Expand Up @@ -306,6 +394,22 @@ def _get_notes(self):
keywords = 'notebook:"{0}"'.format(tools.strip(self.notebook_name))
return GeekNote().findNotes(keywords, 10000).notes

@log
def _get_mtimes(self):
"""
Get modification times
"""
mtfile = open(self.mtime_file, 'rb')
mtimes = pickle.load(mtfile)
return mtimes

@log
def _save_mtimes(self, mtimes):
"""
Save modification times
"""
mtfile = open(self.mtime_file, 'wb')
pickle.dump(mtimes, mtfile)

def main():
try:
Expand All @@ -316,6 +420,7 @@ def main():
parser.add_argument('--notebook', '-n', action='store', help='Notebook name for synchronize. Default is default notebook')
parser.add_argument('--logpath', '-l', action='store', help='Path to log file. Default is GeekNoteSync in home dir')
parser.add_argument('--two-way', '-t', action='store', help='Two-way sync')
parser.add_argument('--merged', '-M', action='store_true', help='Update merged notes to server. Specify it only when conflicts are reported and you have resolved all of them manually on your local files')

args = parser.parse_args()

Expand All @@ -325,10 +430,11 @@ def main():
notebook = args.notebook if args.notebook else None
logpath = args.logpath if args.logpath else None
twoway = True if args.two_way else False
merged = True if args.merged else False

reset_logpath(logpath)

GNS = GNSync(notebook, path, mask, format, twoway)
GNS = GNSync(notebook, path, mask, format, twoway, merged)
GNS.sync()

except (KeyboardInterrupt, SystemExit, tools.ExitException):
Expand Down