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