Pulling in skeleton changes
[quicknote] / src / sync.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 """
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 """
18
19
20 import sys
21 import time
22 import SimpleXMLRPCServer
23 import xmlrpclib
24 import select
25 #import fcntl
26 import uuid
27 import logging
28 import socket
29 socket.setdefaulttimeout(60) # Timeout auf 60 sec. setzen
30
31 import gtk
32 import gobject
33
34
35 try:
36         _
37 except NameError:
38         _ = lambda x: x
39
40
41 _moduleLogger = logging.getLogger(__name__)
42
43
44 class ProgressDialog(gtk.Dialog):
45
46         def __init__(self, title = _("Sync process"), parent = None):
47                 gtk.Dialog.__init__(self, title, parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, ())
48
49                 _moduleLogger.info("ProgressDialog, init")
50
51                 label = gtk.Label(_("Sync process running...please wait"))
52                 self.vbox.pack_start(label, True, True, 0)
53                 label = gtk.Label(_("(this can take some minutes)"))
54                 self.vbox.pack_start(label, True, True, 0)
55
56                 self.vbox.show_all()
57                 self.show()
58
59         def pulse(self):
60                 pass
61
62
63 class Sync(gtk.VBox):
64
65         __gsignals__ = {
66                 'syncFinished' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, )),
67                 'syncBeforeStart' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, )),
68         }
69
70         def __init__(self, db, parentwindow, port):
71                 gtk.VBox.__init__(self, homogeneous = False, spacing = 0)
72
73                 _moduleLogger.info("Sync, init")
74                 self.db = db
75                 self.progress = None
76                 self.server = None
77                 self.port = int(port)
78                 self.parentwindow = parentwindow
79                 self.concernedRows = None
80
81                 sql = "CREATE TABLE sync (id INTEGER PRIMARY KEY, syncpartner TEXT, uuid TEXT, pcdatum INTEGER)"
82                 self.db.speichereSQL(sql, log = False)
83
84                 sql = "SELECT uuid, pcdatum FROM sync WHERE syncpartner = ?"
85                 rows = self.db.ladeSQL(sql, ("self", )) #Eigene Id feststellen
86
87                 if (rows is None)or(len(rows)!= 1):
88                         sql = "DELETE FROM sync WHERE syncpartner = ?"
89                         self.db.speichereSQL(sql, ("self", ), log = False)
90
91                         self.sync_uuid = str(uuid.uuid4())
92                         sql = "INSERT INTO sync (syncpartner, uuid, pcdatum) VALUES (?, ?, ?)"
93                         self.db.speichereSQL(sql, ("self", str(self.sync_uuid), int(time.time())), log = False)
94                 else:
95                         sync_uuid, pcdatum = rows[0]
96                         self.sync_uuid = sync_uuid
97
98                 frame = gtk.Frame(_("Local SyncServer (port ")+str(self.port)+")")
99
100                 self.comboIP = gtk.combo_box_entry_new_text()
101
102                 self.comboIP.append_text("") #self.get_ip_address("eth0"))
103
104                 frame.add(self.comboIP)
105                 serverbutton = gtk.ToggleButton(_("Start/Stop SyncServer"))
106                 serverbutton.connect("clicked", self.startServer, (None, ))
107                 self.pack_start(frame, expand = False, fill = True, padding = 1)
108                 self.pack_start(serverbutton, expand = False, fill = True, padding = 1)
109                 self.syncServerStatusLabel = gtk.Label(_("SyncServer stopped"))
110                 self.pack_start(self.syncServerStatusLabel, expand = False, fill = True, padding = 1)
111
112                 frame = gtk.Frame(_("Remote SyncServer (port ")+str(self.port)+")")
113                 self.comboRemoteIP = gtk.combo_box_entry_new_text()
114                 self.comboRemoteIP.append_text("192.168.0.?")
115                 self.comboRemoteIP.append_text("192.168.1.?")
116                 self.comboRemoteIP.append_text("192.168.176.?")
117                 frame.add(self.comboRemoteIP)
118                 syncbutton = gtk.Button(_("Connect to remote SyncServer"))
119                 syncbutton.connect("clicked", self.syncButton, (None, ))
120                 self.pack_start(frame, expand = False, fill = True, padding = 1)
121                 self.pack_start(syncbutton, expand = False, fill = True, padding = 1)
122                 self.syncStatusLabel = gtk.Label(_("no sync process (at the moment)"))
123                 self.pack_start(self.syncStatusLabel, expand = False, fill = True, padding = 1)
124
125                 self.comboRemoteIP.get_child().set_text(self.db.ladeDirekt("syncRemoteIP"))
126                 self.comboIP.get_child().set_text(self.db.ladeDirekt("syncServerIP"))
127
128                 #load
129                 if self.db.ladeDirekt("startSyncServer", False):
130                         serverbutton.set_active(True)
131
132         def changeSyncStatus(self, active, title):
133                 self.syncStatusLabel.set_text(title)
134                 if active == True:
135                         if self.progress is None:
136                                 self.progress = ProgressDialog(parent = self.parentwindow)
137                                 self.emit("syncBeforeStart", "syncBeforeStart")
138                 else:
139                         if self.progress is not None:
140                                 self.progress.hide()
141                                 self.progress.destroy()
142                                 self.progress = None
143                                 self.emit("syncFinished", "syncFinished")
144
145         def pulse(self):
146                 if self.progress is not None:
147                         self.progress.pulse()
148
149         def getUeberblickBox(self):
150                 frame = gtk.Frame(_("Query"))
151                 return frame
152
153         def handleRPC(self):
154                 try:
155                         if self.rpcserver is None:
156                                 return False
157                 except StandardError:
158                         return False
159
160                 while 0 < len(self.poll.poll(0)):
161                         self.rpcserver.handle_request()
162                 return True
163
164         def get_ip_address(self, ifname):
165                 return socket.gethostbyname(socket.gethostname())
166
167         def getLastSyncDate(self, sync_uuid):
168                 sql = "SELECT syncpartner, pcdatum FROM sync WHERE uuid = ?"
169                 rows = self.db.ladeSQL(sql, (sync_uuid, ))
170                 if rows is not None and len(rows) == 1:
171                         syncpartner, pcdatum = rows[0]
172                 else:
173                         pcdatum = -1
174                 _moduleLogger.info("LastSyncDatum: "+str(pcdatum)+" Jetzt "+str(int(time.time())))
175                 return pcdatum
176
177         def check4commit(self, newSQL, lastdate):
178                 _moduleLogger.info("check4commit 1")
179                 if self.concernedRows is None:
180                         _moduleLogger.info("check4commit Updatung concernedRows")
181                         sql = "SELECT pcdatum, rowid FROM logtable WHERE pcdatum>? ORDER BY pcdatum DESC"
182                         self.concernedRows = self.db.ladeSQL(sql, (lastdate, ))
183
184                 if self.concernedRows is not None and 0 < len(self.concernedRows):
185                         id1, pcdatum, sql, param, host, rowid = newSQL
186
187                         if 0 < len(rowid):
188                                 for x in self.concernedRows:
189                                         if x[1] == rowid:
190                                                 if pcdatum < x[0]:
191                                                         _moduleLogger.info("newer sync entry, ignoring old one")
192                                                         return False
193                                                 else:
194                                                         return True
195
196                 return True
197
198         def writeSQLTupel(self, newSQLs, lastdate):
199                 if newSQLs is None:
200                         return
201
202                 self.concernedRows = None
203                 pausenzaehler = 0
204                 _moduleLogger.info("writeSQLTupel got "+str(len(newSQLs))+" sql tupels")
205                 for newSQL in newSQLs:
206                         if newSQL[3] != "":
207                                 param = newSQL[3].split(" <<Tren-ner>> ")
208                         else:
209                                 param = None
210
211                         if 2 < len(newSQL):
212                                 commitSQL = True
213
214                                 if (newSQL[5]!= None)and(len(newSQL[5])>0):
215                                         commitSQL = self.check4commit(newSQL, lastdate)
216
217                                 if (commitSQL == True):
218                                         self.db.speichereSQL(newSQL[2], param, commit = False, pcdatum = newSQL[1], rowid = newSQL[5])
219                         else:
220                                 _moduleLogger.error("writeSQLTupel: Error")
221
222                         pausenzaehler += 1
223                         if (pausenzaehler % 10) == 0:
224                                 self.pulse()
225                                 while gtk.events_pending():
226                                         gtk.main_iteration()
227
228                 _moduleLogger.info("Alle SQLs an sqlite geschickt, commiting now")
229                 self.db.commitSQL()
230                 _moduleLogger.info("Alle SQLs commited")
231
232         def doSync(self, sync_uuid, pcdatum, newSQLs, pcdatumjetzt):
233                 self.changeSyncStatus(True, "sync process running")
234                 self.pulse()
235
236                 while gtk.events_pending():
237                         gtk.main_iteration()
238                 diff = abs(time.time() - pcdatumjetzt)
239                 if 30 < diff:
240                         return -1
241
242                 _moduleLogger.info("doSync read sqls")
243                 sql = "SELECT * FROM logtable WHERE pcdatum>?"
244                 rows = self.db.ladeSQL(sql, (pcdatum, ))
245                 _moduleLogger.info("doSync read sqls")
246                 self.writeSQLTupel(newSQLs, pcdatum)
247                 _moduleLogger.info("doSync wrote "+str(len(newSQLs))+" sqls")
248                 _moduleLogger.info("doSync sending "+str(len(rows))+" sqls")
249                 return rows
250
251         def getRemoteSyncUUID(self):
252                 return self.sync_uuid
253
254         def startServer(self, widget, data = None):
255                 #Starte RPCServer
256                 self.db.speichereDirekt("syncServerIP", self.comboIP.get_child().get_text())
257
258                 if widget.get_active():
259                         _moduleLogger.info("Starting Server")
260
261                         try:
262                                 ip = self.comboIP.get_child().get_text()
263                                 self.rpcserver = SimpleXMLRPCServer.SimpleXMLRPCServer((ip, self.port), allow_none = True)
264                                 self.rpcserver.register_function(pow)
265                                 self.rpcserver.register_function(self.getLastSyncDate)
266                                 self.rpcserver.register_function(self.doSync)
267                                 self.rpcserver.register_function(self.getRemoteSyncUUID)
268                                 self.rpcserver.register_function(self.doSaveFinalTime)
269                                 self.rpcserver.register_function(self.pulse)
270                                 self.poll = select.poll()
271                                 self.poll.register(self.rpcserver.fileno())
272                                 gobject.timeout_add(1000, self.handleRPC)
273                                 self.syncServerStatusLabel.set_text(_("Syncserver running..."))
274
275                                 #save
276                                 self.db.speichereDirekt("startSyncServer", True)
277
278                         except StandardError:
279                                 s = str(sys.exc_info())
280                                 _moduleLogger.error("libsync: could not start server. Error: "+s)
281                                 mbox = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Could not start SyncServer. Check IP, port settings.")) #gtk.DIALOG_MODAL
282                                 mbox.set_modal(False)
283                                 response = mbox.run()
284                                 mbox.hide()
285                                 mbox.destroy()
286                                 widget.set_active(False)
287                 else:
288                         _moduleLogger.info("Stopping Server")
289                         try:
290                                 del self.rpcserver
291                         except StandardError:
292                                 pass
293                         self.syncServerStatusLabel.set_text(_("SyncServer stopped"))
294                         #save
295                         self.db.speichereDirekt("startSyncServer", False)
296
297         def doSaveFinalTime(self, sync_uuid, pcdatum = None):
298                 if pcdatum is None:
299                         pcdatum = int(time.time())
300                 if pcdatum < time.time():
301                         pcdatum = int(time.time()) #größere Zeit nehmen
302
303                 self.pulse()
304
305                 #fime save time+uuid
306                 sql = "DELETE FROM sync WHERE uuid = ?"
307                 self.db.speichereSQL(sql, (sync_uuid, ), log = False)
308                 sql = "INSERT INTO sync (syncpartner, uuid, pcdatum) VALUES (?, ?, ?)"
309                 self.db.speichereSQL(sql, ("x", str(sync_uuid), pcdatum), log = False)
310                 self.pulse()
311                 self.changeSyncStatus(False, _("no sync process (at the moment)"))
312                 return (self.sync_uuid, pcdatum)
313
314         def syncButton(self, widget, data = None):
315                 _moduleLogger.info("Syncing")
316
317                 self.changeSyncStatus(True, _("sync process running"))
318                 while (gtk.events_pending()):
319                         gtk.main_iteration()
320
321                 self.db.speichereDirekt("syncRemoteIP", self.comboRemoteIP.get_child().get_text())
322                 try:
323                         self.server = xmlrpclib.ServerProxy("http://"+self.comboRemoteIP.get_child().get_text()+":"+str(self.port), allow_none = True)
324                         server_sync_uuid = self.server.getRemoteSyncUUID()
325                         lastDate = self.getLastSyncDate(str(server_sync_uuid))
326
327                         sql = "SELECT * FROM logtable WHERE pcdatum>?"
328                         rows = self.db.ladeSQL(sql, (lastDate, ))
329
330                         _moduleLogger.info("loaded concerned rows")
331
332                         newSQLs = self.server.doSync(self.sync_uuid, lastDate, rows, time.time())
333
334                         _moduleLogger.info("did do sync, processing sqls now")
335                         if newSQLs != -1:
336                                 self.writeSQLTupel(newSQLs, lastDate)
337
338                                 sync_uuid, finalpcdatum = self.server.doSaveFinalTime(self.sync_uuid)
339                                 self.doSaveFinalTime(sync_uuid, finalpcdatum)
340
341                                 self.changeSyncStatus(False, _("no sync process (at the moment)"))
342
343                                 mbox =  gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, _("Synchronization successfully completed"))
344                                 response = mbox.run()
345                                 mbox.hide()
346                                 mbox.destroy()
347                         else:
348                                 _moduleLogger.warning("Zeitdiff zu groß/oder anderer db-Fehler")
349                                 self.changeSyncStatus(False, _("no sync process (at the moment)"))
350                                 mbox =  gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, _("The clocks are not synchronized between stations"))
351                                 response = mbox.run()
352                                 mbox.hide()
353                                 mbox.destroy()
354                 except StandardError:
355                         _moduleLogger.warning("Sync connect failed")
356                         self.changeSyncStatus(False, _("no sync process (at the moment)"))
357                         mbox =  gtk.MessageDialog(
358                                 None,
359                                 gtk.DIALOG_MODAL,
360                                 gtk.MESSAGE_INFO,
361                                 gtk.BUTTONS_OK,
362                                 _("Sync failed, reason: ")+unicode(sys.exc_info()[1][1])
363                         )
364                         response = mbox.run()
365                         mbox.hide()
366                         mbox.destroy()
367                         self.server = None
368                 self.server = None