-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathsync.py
278 lines (218 loc) · 10.3 KB
/
sync.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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
import os
import sys
import traceback
import engine_tools
from filetransfer_abc import ftp_si
from PySide.QtCore import QObject, Slot, Signal, QTimer, QDir, QThread
from dbcore import File, FileAction, ActionQueue, Session, empty_db
from watchers import ServerWatcher, LocalWatcher
class Sync(QObject):
deleteServerFile = Signal((str,))
deleteLocalFile = Signal((str,))
downloadFile = Signal((str,))
uploadFile = Signal((str,))
checkServer = Signal()
checkLocal = Signal()
statusChanged = Signal((str,))
def __init__(self, host, ssl, parent=None):
super(Sync, self).__init__(parent)
self.server = ServerWatcher(host, ssl, self)
self.preloaedActions = []
self.doPreemptive = empty_db()
self.connected = False
self.firstScan = True
def setLocalDir(self, localdir):
if not os.path.exists(localdir):
# Creates the directory if it doesn't already exists.
os.makedirs(localdir)
self.local = LocalWatcher(localdir)
self.server.setLocalDir(localdir)
self.local.moveToThread(self.thread())
self.local.setParent(self)
def connections(self):
if not self.connected:
self.connected = True
self.server.fileAdded.connect(self.onAdded)
self.server.fileChanged.connect(self.onChanged)
self.server.fileDeleted.connect(self.onDeleted)
self.local.fileAdded.connect(self.onAdded)
self.local.fileChanged.connect(self.onChanged)
self.local.fileDeleted.connect(self.onDeleted)
self.deleteLocalFile.connect(self.local.deleteFile)
self.deleteServerFile.connect(self.server.onDelete)
self.downloadFile.connect(self.server.onDownload)
self.uploadFile.connect(self.server.onUpload)
@Slot()
def initQueue(self):
self.actionQueue = ActionQueue()
self.actionTimer = QTimer()
self.actionTimer.setInterval(1)
self.actionTimer.timeout.connect(self.takeAction)
self.actionTimer.start()
@Slot()
def takeAction(self):
self.actionTimer.stop()
if self.doPreemptive:
# Preemptive check is a bit of a workaround to deal with
# initial unexpected conditions: database file is gone
self.doPreemptive = False
self.server.preemptiveCheck = True
self.local.fileAdded.connect(self.server.added)
self.local.checkout()
self.server.checkout()
self.local.fileAdded.disconnect(self.server.added)
self.server.preemptiveCheck = False
for action in self.server.preemptiveActions:
self.actionQueue.add(action)
# After preemptive check, it is safe to do the connections
# for normal operations
self.connections()
serverActionCount = 0
localActionCount = 0
for action in self.actionQueue:
if action is not None:
print 'Next action: %s' % action
path = action.path
do = action.action
location = action.location
if location == FileAction.LOCAL and (do == FileAction.UPLOAD \
or do == FileAction.DELETE):
if not engine_tools.file_exists_local(path):
# File no longer exists at the time of processing.
# Maybe it was temporary or a quick rename.
# So we ignore it
print "Ignored action on " + path + ": File doesn't exist on local."
continue
if do == FileAction.UPLOAD:
self.uploadFile.emit(path)
localActionCount += 1
elif do == FileAction.DOWNLOAD:
self.downloadFile.emit(path)
serverActionCount += 1
elif do == FileAction.DELETE:
with File.fromPath(path) as deleted_file:
# `action.location` attribute only makes sense when deciding
# whether to delete a file on the server or local.
if location == FileAction.LOCAL:
localpath = self.local.localFromServer(path)
self.deleteLocalFile.emit(localpath)
deleted_file.inlocal = False
localActionCount += 1
elif location == FileAction.SERVER:
self.deleteServerFile.emit(path)
deleted_file.inserver = False
serverActionCount += 1
self.actionQueue.clear()
# Scan server for file changes
self.statusChanged.emit('Scanning remote files for changes')
self.server.checkout()
if self.firstScan:
# First do a full scan to check for offline changes.
# From there we will rely on real time notifications watchdog.
self.firstScan = False
self.statusChanged.emit('Scanning local files for changes')
self.local.checkout()
self.local.startObserver()
# Si Added
# Since its the first scan, we should also
# set the timer interval
self.actionTimer.setInterval(5000)
self.cleanSync()
# Si Added
# Set check interval intelligently.
# If there's no activity there, wait longer.
# Since if there's just no usage, then
# no reason to take up CPU cycles.
tempInterval = 0
if serverActionCount+localActionCount > 0:
tempInterval = 5000
else:
tempInterval = 1000 * 10
self.actionTimer.start()
@Slot()
def cleanSync(self):
"""
Removes entries from the database for deleted files
"""
session = Session()
session.query(File).filter(File.inserver == False).filter(File.inlocal == False).delete(synchronize_session=False)
session.commit()
self.statusChanged.emit('Sync completed. Waiting for changes')
@Slot(str, str, bool)
def onChanged(self, location, serverpath, skipDeltaCheck):
changed_file = File.fromPath(serverpath)
action = None
#if not changed_file.servermdate:
# Probably a local added event that also
# spawned a modified event.
#return
file_name_only = os.path.basename(serverpath)
if engine_tools.isTemporaryFile(file_name_only):
print 'File ' + serverpath + ' ignored since it is a temporary file'
return
print 'File ' + serverpath + ':'
if changed_file.servermdate == None:
mydiff = "** File Not in Server **"
edit_time = "(not in server)"
else:
ttt = (changed_file.localmdate - changed_file.servermdate).total_seconds()
mydiff = str( ttt )
edit_time = str(changed_file.servermdate)
print 'Changed here %s, there %s delta %s' % (
changed_file.localmdate, edit_time, mydiff)
try:
if changed_file.inserver:
diff = changed_file.timeDiff()
MY_TOLERANCE = 10
if skipDeltaCheck == False and abs(diff) < MY_TOLERANCE:
return
if location == FileAction.SERVER:
if changed_file.inlocal:
if changed_file.localmdate < changed_file.servermdate:
action = FileAction(serverpath, FileAction.DOWNLOAD, FileAction.LOCAL)
else:
action = FileAction(serverpath, FileAction.DOWNLOAD, FileAction.LOCAL)
elif location == FileAction.LOCAL:
if changed_file.inserver:
try:
if changed_file.servermdate < changed_file.localmdate:
action = FileAction(serverpath, FileAction.UPLOAD, FileAction.SERVER)
except:
print 'Error:', changed_file, changed_file.servermdate, changed_file.localmdate
else:
action = FileAction(serverpath, FileAction.UPLOAD, FileAction.SERVER)
if action is not None:
self.actionQueue.add(action)
except:
info = traceback.format_exception(*sys.exc_info())
for i in info: sys.stderr.write(i)
@Slot(str, str)
def onAdded(self, location, serverpath):
file_name_only = os.path.basename(serverpath)
if engine_tools.isTemporaryFile(file_name_only):
print 'File ' + serverpath + ' was created but ignored since it is a temporary file'
return
added_file = File.fromPath(serverpath)
action = None
if location == FileAction.SERVER and not added_file.inlocal:
action = FileAction(serverpath, FileAction.DOWNLOAD, FileAction.LOCAL)
elif location == FileAction.LOCAL and not added_file.inserver:
action = FileAction(serverpath, FileAction.UPLOAD, FileAction.SERVER)
if action is not None:
self.actionQueue.add(action)
@Slot(str, str)
def onDeleted(self, location, serverpath):
# NOTE: For temporary files, the current action is to delete it.
# Reason 1: We need to remove it from the database.
# Reason 2: If somehow there is a temporary file
# there on the other side, then it makes sense to delete it.
deleted_file = File.fromPath(serverpath)
action = None
if location == FileAction.SERVER:
if deleted_file.inlocal:
action = FileAction(serverpath, FileAction.DELETE, FileAction.LOCAL)
elif location == FileAction.LOCAL:
if deleted_file.inserver:
action = FileAction(serverpath, FileAction.DELETE, FileAction.SERVER)
if action is not None:
self.actionQueue.add(action)