-
Notifications
You must be signed in to change notification settings - Fork 5
/
main.py
219 lines (195 loc) · 8.08 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
'''
Bear Notes App Exporter and Sync
This script will export new files from the SQLite database where Bear stores
all notes. It assumes that every note has exactly one tag and uses that as the
filepath in the exported files.
It will additionally sync files that already exist in the database, taking
either the exported or the database version depending on the date they were
last modified.
It will NOT import new files into the database -- please use Bear's built-in
import function for this. Once the file has been imported it can be synced as
normal.
'''
from sqlalchemy import create_engine
import pandas as pd
import os
from datetime import datetime
from time import sleep
os.stat_float_times(True)
MAC_USERNAME = 'yourUsernameHere'
ROOT = '~/Applications/bear-sync/sync/'
def main():
# Connect to Bear database.
db = create_engine('sqlite:////Users/' + MAC_USERNAME + '/Library/' +
'Containers/net.shinyfrog.bear/Data/Documents/' +
'Application Data/database.sqlite')
# Build list of files in the DB.
dbNotes = pd.read_sql(
"SELECT a.Z_PK as NID, a.ZTITLE || '.' || a.Z_PK || '.md' as FILE,\
a.ZTEXT as CONTENT, CAST(a.ZMODIFICATIONDATE as REAL) as DATE,\
c.ZTITLE as TAG_PATH, MAX(LENGTH(c.ZTITLE)) AS TAG_LEN,\
a.ZTRASHED as TRASHED\
FROM ( SELECT * FROM ZSFNOTE WHERE ZSKIPSYNC = 0 ) a\
LEFT JOIN Z_5TAGS b ON a.Z_PK = b.Z_5NOTES\
LEFT JOIN ZSFNOTETAG c ON b.Z_10TAGS = c.Z_PK\
GROUP BY FILE;",
db
).set_index('NID')
dbNotes.loc[dbNotes['TAG_PATH'].isnull(), 'TAG_PATH'] = ''
dbNotes = dbNotes.to_dict(orient='index')
# Build list of files on the FS.
fsNotes = {}
for path, _, files in os.walk(ROOT):
# Include only .md Markdown files.
files = [f for f in files if (
os.path.splitext(f)[1] == '.md' and
f[0] != '.'
)]
# Go through every note in the folder.
for note in files:
# Get the unique note ID.
nid = os.path.splitext(os.path.splitext(note)[0])[1][1:]
# Test the note ID to make sure it's an integer.
try:
int(nid)
except ValueError:
# If it's not an integer, skip it.
continue
else:
# Get the contents of the note.
with open(os.path.join(path, note), encoding='utf-8') as f:
content = f.read()
# Add an entry.
fsNotes[int(nid)] = {
'FILE': note,
'CONTENT': content,
'DATE': os.path.getmtime(os.path.join(path, note)) +
datetime(1970, 1, 1).timestamp() -
datetime(2001, 1, 1).timestamp(),
'TAG_PATH': os.path.relpath(path, ROOT),
}
# Go through every file in the DB and compare it with the current FS
# version.
for nid in dbNotes:
try:
fsNotes[nid]
except KeyError: # Note doesn't exist on the FS, so create it.
if dbNotes[nid]['TRASHED'] == 0:
# Create the tag path.
try:
os.makedirs(os.path.join(ROOT, dbNotes[nid]['TAG_PATH']))
except OSError as e:
pass
# Make the file.
with open(
os.path.join(
ROOT,
dbNotes[nid]['TAG_PATH'],
dbNotes[nid]['FILE']
),
'w', encoding='utf-8'
) as f:
f.write(dbNotes[nid]['CONTENT'])
# Update the DB with new time.
date = os.path.getmtime(
os.path.join(
ROOT,
dbNotes[nid]['TAG_PATH'],
dbNotes[nid]['FILE']
)
) + datetime(1970, 1, 1).timestamp() - datetime(2001, 1, 1).timestamp()
db.execute(
"UPDATE ZSFNOTE\
SET ZMODIFICATIONDATE = " + str(date) + "\
WHERE Z_PK = " + str(nid) + ";"
)
else: # Note already exists, so compare details.
# First, make sure the note hasn't been trashed on the DB.
# NB: trashing a note from the FS will not work, as it will be
# re-added on the next sync. To trash a note you must use Bear.
if dbNotes[nid]['TRASHED'] != 0:
try:
os.remove(
os.path.join(
ROOT,
fsNotes[nid]['TAG_PATH'],
fsNotes[nid]['FILE']
)
)
except FileNotFoundError:
pass
else:
# Next, check if the DB filename or tag path has changed.
if (
dbNotes[nid]['FILE'] != fsNotes[nid]['FILE'] or
dbNotes[nid]['TAG_PATH'] != fsNotes[nid]['TAG_PATH']
):
# Update the FS filename and path to match the DB version.
# NB: to change the path of a note on the FS, update the
# tags inside the note, and wait for the sync with Bear
# to complete.
# Create the tag path.
try:
os.makedirs(os.path.join(ROOT, dbNotes[nid]['TAG_PATH']))
except OSError as e:
pass
os.rename(
os.path.join(
ROOT,
fsNotes[nid]['TAG_PATH'],
fsNotes[nid]['FILE']
),
os.path.join(
ROOT,
dbNotes[nid]['TAG_PATH'],
dbNotes[nid]['FILE']
)
)
fsNotes[nid]['TAG_PATH'] = dbNotes[nid]['TAG_PATH']
fsNotes[nid]['FILE'] = dbNotes[nid]['FILE']
# Now compare the dates and sync.
if dbNotes[nid]['DATE'] > fsNotes[nid]['DATE']:
# Save DB to FS.
with open(
os.path.join(
ROOT,
fsNotes[nid]['TAG_PATH'],
fsNotes[nid]['FILE']
),
'w',
encoding='utf-8'
) as f:
f.write(dbNotes[nid]['CONTENT'])
elif dbNotes[nid]['DATE'] < fsNotes[nid]['DATE']:
# Save FS to DB.
db.execute(
"UPDATE ZSFNOTE\
SET ZTEXT = \"" + fsNotes[nid]['CONTENT'] + "\"\
WHERE Z_PK = " + str(nid) + ";"
)
# Update the DB with new time.
date = os.path.getmtime(
os.path.join(
ROOT,
fsNotes[nid]['TAG_PATH'],
fsNotes[nid]['FILE']
)
) + datetime(1970, 1, 1).timestamp() - datetime(2001, 1, 1).timestamp()
db.execute(
"UPDATE ZSFNOTE\
SET ZMODIFICATIONDATE = " + str(date) + "\
WHERE Z_PK = " + str(nid) + ";"
)
# Go back through the FS and remove empty folders.
for path, folders, files in os.walk(ROOT):
# Include only .md Markdown files.
files = [f for f in files if (
os.path.splitext(f)[1] == '.md' and
f[0] != '.'
)]
if len(files) == 0 and len(folders) == 0:
os.rmdir(path)
if __name__ == "__main__":
while True:
main()
sleep(10)