Skip to content

Commit a5ce00c

Browse files
committed
Public Upload
1 parent ca36839 commit a5ce00c

File tree

11 files changed

+1573
-0
lines changed

11 files changed

+1573
-0
lines changed

.editorconfig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# editorconfig.org
2+
root = true
3+
4+
# Defaults
5+
[*]
6+
end_of_line = lf
7+
charset = utf-8
8+
trim_trailing_whitespace = true
9+
insert_final_newline = true
10+
11+
# Python
12+
[*.py]
13+
indent_style = space
14+
indent_size = 4

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Project specific stuff
2+
testing/
3+
14
# Byte-compiled / optimized / DLL files
25
__pycache__/
36
*.py[cod]

README.rst

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
snsync, like rsync for Simplenote
2+
##################################
3+
4+
snsync is a kinda rsync implementation for Simplenote where your notes can be downloaded (& and uploaded) from plain text files.
5+
6+
The primary use case is for periodic synchronisation by cron, with all the *useful* output going to a log file and the console output being *pretty* for when humans sync manually.
7+
8+
The configuration file
9+
----------------------
10+
11+
By default, you need `~/.snsync` but the command line options do allow to select another file, the minimal info needed is a username and password::
12+
13+
[snsync]
14+
cfg_sn_username = me@here.com
15+
cfg_sn_password = secret
16+
17+
*IMPORTANT! Protect your .snsync file with the correct permissions and disk encryption*
18+
19+
A few additional options are possible:
20+
21+
* `cfg_nt_path = /Users/linickx/mynotes` to change the default note path (`~/Simplenote`)
22+
* `cfg_log_path = /Users/Library/Logs/snsync.log` to change the default log path (which is typically within `cfg_nt_path`). Use the keyword `DISABLED` to enable console logging.
23+
* `cfg_log_level = debug` the default logging level is `info`, the brave can change this to `error`, ninja's can enable `debug`
24+
25+
The command line options
26+
------------------------
27+
28+
The following usage/options are available::
29+
30+
Usage: snsync [OPTIONS]
31+
32+
OPTIONS:
33+
-h, --help Help!
34+
-d, --dry-run Dry Run Mode (no changes made/saved)
35+
-s, --silent Silent Mode (no std output)
36+
-c, --config= Config file to read (default: ~/.snsync)
37+
38+
For example: just `snsync` on it's own should work, but something like this can be used for cron: `snsync -s --config=something.txt`
39+
40+
File Deletions
41+
--------------
42+
43+
snsync doesn't delete any files, you can check the source code yourself ;)
44+
45+
When a file is marked for deletion on Simplenote, the local note (*text file*) equivalent is moved to a `.trash` directory. When a file is deleted locally the Simplenote equivalent is marked with Trash tag.
46+
47+
File Conflicts
48+
--------------
49+
50+
If your cron job is very sporadic it possible that a change could be made on the Simplenote server and locally, when this happens the local file is renamed, for example `hello world.txt` would become `DUP_date_hello world.txt` (*where date is the date/time the file was moved*). Duplicates are then uploaded back into Simplenote for safe keeping.
51+
52+
Local file names are based on the first line of the Simplenote "note". Filenames are generated on a first come, first served basis, for example if you create a Simplenote online with the first line "hello world" then `hello world.txt` will be created, if you create a 2nd note, with completely different contents but the first line is "hello world" then the 2nd file will be called `date_hello world.txt` (*where date is the date/time the file was created*)
53+
54+
File Modifications
55+
------------------
56+
57+
snsync works by maintaining a local sqlite database, typically `.snsycn.sqlite` inside your `cfg_nt_path`. The database maintains a copy of the Simplenote list and a meta table that links Simplenotes to text files.
58+
59+
The script works by comparing the latest Simplenote list to the local cache, and then compares the last modified dates of local files; moves/adds/changes/deletions are then replicated by-directionally. The `--dry-run` option can be used to observe what is going to happen without making any changes.
60+
61+
For those wondering what the log file strings like `agtzaW1wbZRiusssu5sIDAasdfuhas` are; that's the "key" used in the Simplenote cloud to store your note, the local meta database keeps track of those and associates a file name... the cloud don't need no file names dude! ;-)
62+
63+
Large Note Databases
64+
--------------------
65+
66+
The Simplenote API is rate limited, if your note database is large (like mine -> 1,200 notes) then the first full sync will take a long time (mine -> approx 15mins) you will also find a high number of `HTTP ERRORS` reported, just wait and re-run the script, missed notes will be downloaded.
67+
68+
AoB
69+
---
70+
71+
No warranty is offered, use this at your own risk; I use this for my personal production notes but I always keep backups. The recommended approach is to manually download all your notes for a backup, then use the `--dry-run` option to observe changes until you are happy.
72+
73+
Credz, props and big-ups to https://github.com/insanum/sncli and https://github.com/mrtazz/Simplenote.py as without these opensource projects, snsync would not have got off the ground :)

docs/snsync_screenshot.gif

40.8 KB
Loading

simplenote_sync/config.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""
2+
Configuration settings for snsync
3+
"""
4+
# pylint: disable=W0702
5+
# pylint: disable=C0301
6+
7+
import os
8+
import collections
9+
import configparser
10+
11+
class Config:
12+
"""
13+
Config Object
14+
"""
15+
16+
def __init__(self, custom_file=None):
17+
"""
18+
Defult settings and the like.
19+
"""
20+
self.home = os.path.abspath(os.path.expanduser('~'))
21+
# Static Defaults
22+
defaults = \
23+
{
24+
'cfg_sn_username' : '',
25+
'cfg_sn_password' : '',
26+
'cfg_nt_ext' : 'txt',
27+
'cfg_nt_path' : os.path.join(self.home, 'Simplenote'),
28+
'cfg_nt_trashpath' : '.trash',
29+
'cfg_nt_filenamelen' : '60',
30+
'cfg_log_level' : 'info'
31+
}
32+
33+
cp = configparser.SafeConfigParser(defaults)
34+
if custom_file is not None:
35+
self.configs_read = cp.read([custom_file])
36+
else:
37+
self.configs_read = cp.read([os.path.join(self.home, '.snsync')])
38+
39+
cfg_sec = 'snsync'
40+
41+
if not cp.has_section(cfg_sec):
42+
cp.add_section(cfg_sec)
43+
44+
self.configs = collections.OrderedDict()
45+
self.configs['sn_username'] = [cp.get(cfg_sec, 'cfg_sn_username', raw=True), 'Simplenote Username']
46+
self.configs['sn_password'] = [cp.get(cfg_sec, 'cfg_sn_password', raw=True), 'Simplenote Password']
47+
self.configs['cfg_nt_ext'] = [cp.get(cfg_sec, 'cfg_nt_ext'), 'Note file extension']
48+
self.configs['cfg_nt_path'] = [cp.get(cfg_sec, 'cfg_nt_path'), 'Note storage path']
49+
self.configs['cfg_nt_trashpath'] = [cp.get(cfg_sec, 'cfg_nt_trashpath'), 'Note Trash Bin Folder for deleted notes']
50+
self.configs['cfg_nt_filenamelen'] = [cp.get(cfg_sec, 'cfg_nt_filenamelen'), 'Length of Filename']
51+
self.configs['cfg_log_level'] = [cp.get(cfg_sec, 'cfg_log_level'), 'snsync log level']
52+
53+
# Dynamic Defaults
54+
if cp.has_option(cfg_sec, 'cfg_db_path'):
55+
self.configs['cfg_db_path'] = [cp.get(cfg_sec, 'cfg_db_path'), 'snsync database location']
56+
else:
57+
self.configs['cfg_db_path'] = [os.path.join(cp.get(cfg_sec, 'cfg_nt_path'), '.snsync.sqlite'), 'snsync database location']
58+
59+
if cp.has_option(cfg_sec, 'cfg_log_path'):
60+
self.configs['cfg_log_path'] = [cp.get(cfg_sec, 'cfg_log_path'), 'snsync log location']
61+
else:
62+
self.configs['cfg_log_path'] = [os.path.join(cp.get(cfg_sec, 'cfg_nt_path'), '.snsync.log'), 'snsync log location']
63+
64+
def get_config(self, name):
65+
"""
66+
Return a config setting
67+
"""
68+
return self.configs[name][0]
69+
70+
def get_config_descr(self, name):
71+
"""
72+
Return a config description (future use in docs)
73+
"""
74+
return self.configs[name][1]

0 commit comments

Comments
 (0)