Bug fixes that were apart of the previous commits bump
[multilist] / src / libsync.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 import sys
20 import select
21 import uuid
22 import time
23 import string
24 from SimpleXMLRPCServer import SimpleXMLRPCServer
25 import socket
26 import xmlrpclib
27 import logging
28
29 import gobject
30 import gtk
31
32 import gtk_toolbox
33
34
35 try:
36         _
37 except NameError:
38         _ = lambda x: x
39
40
41 _moduleLogger = logging.getLogger(__name__)
42 socket.setdefaulttimeout(60) # Timeout auf 60 sec. setzen 
43
44
45 class ProgressDialog(gtk.Dialog):
46
47         def __init__(self, title = _("Sync process"), parent = None):
48                 gtk.Dialog.__init__(self, title, parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, ())
49
50                 _moduleLogger.info("ProgressDialog, init")
51
52                 label = gtk.Label(_("Sync process running...please wait"))
53                 self.vbox.pack_start(label, True, True, 0)
54                 label = gtk.Label(_("(this can take some minutes)"))
55                 self.vbox.pack_start(label, True, True, 0)
56
57                 #self.progressbar = gtk.ProgressBar()
58                 #self.vbox.pack_start(self.progressbar, True, True, 0)
59
60                 #self.set_keep_above(True)
61                 self.vbox.show_all()
62                 self.show()
63
64         def pulse(self):
65                 #self.progressbar.pulse()
66                 pass
67
68
69 class Sync(gtk.VBox):
70
71         __gsignals__ = {
72                 'syncFinished' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, )),
73                 'syncBeforeStart' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, )),
74         }
75
76         def __init__(self, db, parentwindow, port):
77                 gtk.VBox.__init__(self, homogeneous = False, spacing = 0)
78
79                 _moduleLogger.info("Sync, init")
80                 self.db = db
81                 self.progress = None
82                 self.server = None
83                 self.port = int(port)
84                 self.parentwindow = parentwindow
85                 self.concernedRows = None
86
87                 #print "Sync, 2"
88                 #sql = "DROP TABLE sync"
89                 #self.db.speichereSQL(sql, log = False)
90
91                 sql = "CREATE TABLE sync (id INTEGER PRIMARY KEY, syncpartner TEXT, uuid TEXT, pcdatum INTEGER)"
92                 self.db.speichereSQL(sql, log = False)
93
94                 #print "Sync, 3"
95
96                 sql = "SELECT uuid, pcdatum FROM sync WHERE syncpartner = ?"
97                 rows = self.db.ladeSQL(sql, ("self", )) #Eigene Id feststellen
98
99                 #print "Sync, 3a"
100                 if (rows == None)or(len(rows) != 1):
101                         sql = "DELETE FROM sync WHERE syncpartner = ?"
102                         self.db.speichereSQL(sql, ("self", ), log = False)
103
104                         #uuid1 = uuid()
105                         #print "Sync, 3b"
106
107                         #print "Sync, 3bb"
108                         self.sync_uuid = str(uuid.uuid4())
109                         sql = "INSERT INTO sync (syncpartner, uuid, pcdatum) VALUES (?, ?, ?)"
110                         self.db.speichereSQL(sql, ("self", str(self.sync_uuid), int(time.time())), log = False)
111                         #print "Sync, 3c"
112                 else:
113                         sync_uuid, pcdatum = rows[0]
114                         self.sync_uuid = sync_uuid
115                 #print "x1"
116                 #print "Sync, 4"
117
118                 frame = gtk.Frame(_("Local SyncServer (port ")+str(self.port)+")")
119                 self.comboIP = gtk.combo_box_entry_new_text()
120                 self.comboIP.append_text("") #self.get_ip_address("eth0"))
121                 #self.comboIP.append_text(self.get_ip_address("eth1")) #fixme
122                 #self.comboIP.append_text(self.get_ip_address("eth2"))
123                 #self.comboIP.append_text(self.get_ip_address("eth3"))
124                 #print "Sync, 4d"
125                 #self.comboIP.append_text(self.get_ip_address("wlan0"))
126                 #self.comboIP.append_text(self.get_ip_address("wlan1"))
127
128                 #print "Sync, 4e"
129
130                 frame.add(self.comboIP)
131                 serverbutton = gtk.ToggleButton(_("Start SyncServer"))
132                 serverbutton.connect("clicked", self.startServer, (None, ))
133                 self.pack_start(frame, expand = False, fill = True, padding = 1)
134                 self.pack_start(serverbutton, expand = False, fill = True, padding = 1)
135                 self.syncServerStatusLabel = gtk.Label(_("Syncserver not running"))
136                 self.pack_start(self.syncServerStatusLabel, expand = False, fill = True, padding = 1)
137
138                 frame = gtk.Frame(_("RemoteSync-Server (Port ")+str(self.port)+")")
139                 self.comboRemoteIP = gtk.combo_box_entry_new_text()
140                 self.comboRemoteIP.append_text("192.168.0.?")
141                 self.comboRemoteIP.append_text("192.168.1.?")
142                 self.comboRemoteIP.append_text("192.168.176.?")
143                 frame.add(self.comboRemoteIP)
144                 syncbutton = gtk.Button(_("Connect to remote SyncServer"))
145                 syncbutton.connect("clicked", self.syncButton, (None, ))
146                 self.pack_start(frame, expand = False, fill = True, padding = 1)
147                 self.pack_start(syncbutton, expand = False, fill = True, padding = 1)
148                 self.syncStatusLabel = gtk.Label(_("no sync process (at the moment)"))
149                 self.pack_start(self.syncStatusLabel, expand = False, fill = True, padding = 1)
150
151                 #self.comboRemoteIP.set_text_column("Test")
152                 self.comboRemoteIP.get_child().set_text(self.db.ladeDirekt("syncRemoteIP"))
153                 self.comboIP.get_child().set_text(self.db.ladeDirekt("syncServerIP"))
154
155                 #load
156                 if (self.db.ladeDirekt("startSyncServer", False) == True):
157                         serverbutton.set_active(True)
158
159         def changeSyncStatus(self, active, title):
160                 self.syncStatusLabel.set_text(title)
161                 if active == True:
162                         if self.progress == None:
163                                 self.progress = ProgressDialog(parent = self.parentwindow)
164                                 self.emit("syncBeforeStart", "syncBeforeStart")
165                 else:
166                         if self.progress is not None:
167                                 self.progress.hide()
168                                 self.progress.destroy()
169                                 self.progress = None
170                                 self.emit("syncFinished", "syncFinished")
171
172         def pulse(self):
173                 if self.progress is not None:
174                         self.progress.pulse()
175                 #if self.server is not None:
176                 #       self.server.pulse()
177
178         def getUeberblickBox(self):
179                 frame = gtk.Frame(_("Query"))
180                 return frame
181
182         def handleRPC(self):
183                 try:
184                         if self.rpcserver is None:
185                                 return False
186                 except:
187                         return False
188
189                 while 0 < len(self.poll.poll(0)):
190                         self.rpcserver.hande_request()
191                 return True
192
193         def get_ip_address(self, ifname):
194                 return socket.gethostbyname(socket.gethostname())
195                 #try:
196                 #       s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
197                 #       ip = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24])
198                 #       s.close()
199                 #except:
200                 #       ip = socket.gethostbyname(socket.gethostname())
201                 #       s.close()
202
203                 #return ip FixME
204
205         def getLastSyncDate(self, sync_uuid):
206                 sql = "SELECT syncpartner, pcdatum FROM sync WHERE uuid = ?"
207                 rows = self.db.ladeSQL(sql, (sync_uuid, ))
208                 if (rows is not None)and(len(rows) == 1):
209                         syncpartner, pcdatum = rows[0]
210                 else:
211                         pcdatum = -1
212                 _moduleLogger.info("LastSyncDatum: "+str(pcdatum)+" Jetzt "+str(int(time.time())))
213                 return pcdatum
214
215         def check4commit(self, newSQL, lastdate):
216                 _moduleLogger.info("check4commit 1")
217                 if self.concernedRows == None:
218                         _moduleLogger.info("check4commit Updatung concernedRows")
219                         sql = "SELECT pcdatum, rowid FROM logtable WHERE pcdatum>? ORDER BY pcdatum DESC"
220                         self.concernedRows = self.db.ladeSQL(sql, (lastdate, ))
221
222                 if (self.concernedRows is not None)and(len(self.concernedRows)>0):
223                         #_moduleLogger.info("check4commit 2")
224                         id1, pcdatum, sql, param, host, rowid = newSQL
225
226                         if len(rowid)>0:
227                                 for x in self.concernedRows:
228                                         #_moduleLogger.info("check4commit 3")
229                                         if (x[1] == rowid):
230                                                 if (x[0]>pcdatum):
231                                                         _moduleLogger.info("newer sync entry, ignoring old one")
232                                                         #_moduleLogger.info("check4commit 9.1")
233                                                         return False
234                                                 else:
235                                                         #_moduleLogger.info("check4commit 9.2")
236                                                         return True
237
238                 #_moduleLogger.info("check4commit 9.3")
239                 return True
240
241         def writeSQLTupel(self, newSQLs, lastdate):
242                 if newSQLs is None:
243                         return
244
245                 self.concernedRows = None
246                 pausenzaehler = 0
247                 _moduleLogger.info("writeSQLTupel got "+str(len(newSQLs))+" sql tupels")
248                 for newSQL in newSQLs:
249                         #print ""
250                         #print "SQL1: ", newSQL[1]
251                         #print "SQL2: ", newSQL[2]
252                         #print "SQL3: ", newSQL[3]
253                         #print "Param:", string.split(newSQL[3], " <<Tren-ner>> ")
254                         #print ""
255                         if (newSQL[3] != ""):
256                                 param = string.split(newSQL[3], " <<Tren-ner>> ")
257                         else:
258                                 param = None
259
260                         if (len(newSQL)>2):
261                                 commitSQL = True
262
263                                 if (newSQL[5] is not None)and(len(newSQL[5])>0):
264                                         commitSQL = self.check4commit(newSQL, lastdate)
265
266                                 if commitSQL:
267                                         self.db.speichereSQL(newSQL[2], param, commit = False, pcdatum = newSQL[1], rowid = newSQL[5])
268                         else:
269                                 _moduleLogger.error("writeSQLTupel: Error")
270
271                         pausenzaehler += 1
272                         if (pausenzaehler % 10) == 0:
273                                 self.pulse()
274                                 while gtk.events_pending():
275                                         gtk.main_iteration()
276
277                 _moduleLogger.info("Alle SQLs an sqlite geschickt, commiting now")
278                 self.db.commitSQL()
279                 _moduleLogger.info("Alle SQLs commited")
280
281         def doSync(self, sync_uuid, pcdatum, newSQLs, pcdatumjetzt):
282                 #print uuid, pcdatum, newSQLs
283                 #_moduleLogger.info("doSync 0")
284                 self.changeSyncStatus(True, _("sync process running"))
285                 self.pulse()
286                 #_moduleLogger.info("doSync 1")
287
288                 while gtk.events_pending():
289                         gtk.main_iteration();
290                 diff = abs(time.time() - pcdatumjetzt)
291                 if 30 < diff:
292                         return -1
293
294                 _moduleLogger.info("doSync read sqls")
295                 sql = "SELECT * FROM logtable WHERE pcdatum>?"
296                 rows = self.db.ladeSQL(sql, (pcdatum, ))
297                 _moduleLogger.info("doSync read sqls")
298                 self.writeSQLTupel(newSQLs, pcdatum)
299                 _moduleLogger.info("doSync wrote "+str(len(newSQLs))+" sqls")
300                 _moduleLogger.info("doSync sending "+str(len(rows))+" sqls")
301                 return rows
302
303         def getRemoteSyncUUID(self):
304                 return self.sync_uuid
305
306         @gtk_toolbox.log_exception(_moduleLogger)
307         def startServer(self, widget, data = None):
308                 #Starte RPCServer
309                 self.db.speichereDirekt("syncServerIP", self.comboIP.get_child().get_text())
310
311                 if (widget.get_active() == True):
312                         _moduleLogger.info("Starting Server")
313
314                         try:
315                                 ip = self.comboIP.get_child().get_text()
316                                 self.rpcserver = SimpleXMLRPCServer((ip, self.port), allow_none = True)
317                                 self.rpcserver.register_function(pow)
318                                 self.rpcserver.register_function(self.getLastSyncDate)
319                                 self.rpcserver.register_function(self.doSync)
320                                 self.rpcserver.register_function(self.getRemoteSyncUUID)
321                                 self.rpcserver.register_function(self.doSaveFinalTime)
322                                 self.rpcserver.register_function(self.pulse)
323                                 self.poll = select.poll()
324                                 self.poll.register(self.rpcserver.fileno())
325                                 gobject.timeout_add(1000, self.handleRPC)
326                                 self.syncServerStatusLabel.set_text(_("Syncserver running..."))
327
328                                 #save
329                                 self.db.speichereDirekt("startSyncServer", True)
330
331                         except:
332                                 s = str(sys.exc_info())
333                                 _moduleLogger.error("libsync: could not start server. Error: "+s)
334                                 mbox = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Sync server could not start. Please check IP and port.")) #gtk.DIALOG_MODAL
335                                 mbox.set_modal(False)
336                                 response = mbox.run()
337                                 mbox.hide()
338                                 mbox.destroy()
339                                 widget.set_active(False)
340                 else:
341                         _moduleLogger.info("Stopping Server")
342                         try:
343                                 del self.rpcserver
344                         except:
345                                 pass
346                         self.syncServerStatusLabel.set_text(_("Syncserver not running..."))
347                         #save
348                         self.db.speichereDirekt("startSyncServer", False)
349
350         def doSaveFinalTime(self, sync_uuid, pcdatum = None):
351                 if pcdatum is None:
352                         pcdatum = int(time.time())
353                 if (time.time()>pcdatum):
354                         pcdatum = int(time.time()) #größere Zeit nehmen
355
356                 self.pulse()
357
358                 #fime save time+uuid
359                 sql = "DELETE FROM sync WHERE uuid = ?"
360                 self.db.speichereSQL(sql, (sync_uuid, ), log = False)
361                 sql = "INSERT INTO sync (syncpartner, uuid, pcdatum) VALUES (?, ?, ?)"
362                 self.db.speichereSQL(sql, ("x", str(sync_uuid), pcdatum), log = False)
363                 self.pulse()
364                 self.changeSyncStatus(False, _("no sync process (at the moment)"))
365                 return (self.sync_uuid, pcdatum)
366
367         @gtk_toolbox.log_exception(_moduleLogger)
368         def syncButton(self, widget, data = None):
369                 _moduleLogger.info("Syncing")
370                 #sql = "DELETE FROM logtable WHERE sql LIKE externeStundenplanung"
371                 #self.db.speichereSQL(sql)
372
373                 self.changeSyncStatus(True, _("sync process running"))
374                 while (gtk.events_pending()):
375                         gtk.main_iteration()
376
377                 self.db.speichereDirekt("syncRemoteIP", self.comboRemoteIP.get_child().get_text())
378                 try:
379                         self.server = xmlrpclib.ServerProxy("http://"+self.comboRemoteIP.get_child().get_text()+":"+str(self.port), allow_none = True)
380                         #lastDate = server.getLastSyncDate(str(self.sync_uuid))
381                         server_sync_uuid = self.server.getRemoteSyncUUID()
382                         lastDate = self.getLastSyncDate(str(server_sync_uuid))
383
384                         #print ("LastSyncDate: "+str(lastDate)+" Now: "+str(int(time.time())))
385
386                         sql = "SELECT * FROM logtable WHERE pcdatum>?"
387                         rows = self.db.ladeSQL(sql, (lastDate, ))
388
389                         _moduleLogger.info("loaded concerned rows")
390
391                         newSQLs = self.server.doSync(self.sync_uuid, lastDate, rows, time.time())
392
393                         _moduleLogger.info("did do sync, processing sqls now")
394                         if newSQLs != -1:
395                                 self.writeSQLTupel(newSQLs, lastDate)
396
397                                 sync_uuid, finalpcdatum = self.server.doSaveFinalTime(self.sync_uuid)
398                                 self.doSaveFinalTime(sync_uuid, finalpcdatum)
399
400                                 self.changeSyncStatus(False, _("no sync process (at the moment)"))
401
402                                 mbox =  gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, _("Synchronization successfully completed"))
403                                 response = mbox.run()
404                                 mbox.hide()
405                                 mbox.destroy()
406                         else:
407                                 _moduleLogger.warning("Zeitdiff zu groß/oder anderer db-Fehler")
408                                 self.changeSyncStatus(False, _("no sync process (at the moment)"))
409                                 mbox =  gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, _("The clocks are not synchronized between stations"))
410                                 response = mbox.run()
411                                 mbox.hide()
412                                 mbox.destroy()
413                 except:
414                         _moduleLogger.warning("Sync connect failed")
415                         self.changeSyncStatus(False, _("no sync process (at the moment)"))
416                         mbox =  gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, _("Sync failed, reason: ")+unicode(sys.exc_info()[1][1]))
417                         response = mbox.run()
418                         mbox.hide()
419                         mbox.destroy()
420                         self.server = None
421                 self.server = None