TIny cleanups
[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!=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!=None:
172                         self.progress.pulse()
173                 #if self.server!=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==None): return False
183                 except:
184                         return False
185
186                 while (len(self.poll.poll(0))>0):
187                         self.rpcserver.handle_request()
188                 return True
189
190         def get_ip_address(self,ifname):
191                 return socket.gethostbyname(socket.gethostname())
192                 #try:
193                 #       s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
194                 #       ip=socket.inet_ntoa(fcntl.ioctl(s.fileno(),0x8915,struct.pack('256s', ifname[:15]))[20:24])
195                 #       s.close()
196                 #except:
197                 #       ip=socket.gethostbyname(socket.gethostname())
198                 #       s.close()
199
200                 #return ip FixME
201
202         def getLastSyncDate(self,sync_uuid):
203                 sql="SELECT syncpartner,pcdatum FROM sync WHERE uuid=?"
204                 rows=self.db.ladeSQL(sql,(sync_uuid,))
205                 if (rows!=None)and(len(rows)==1):
206                         syncpartner,pcdatum = rows[0]
207                 else:
208                         pcdatum=-1
209                 _moduleLogger.info("LastSyncDatum: "+str(pcdatum)+" Jetzt "+str(int(time.time())))
210                 return pcdatum
211
212         def check4commit(self,newSQL,lastdate):
213                 _moduleLogger.info("check4commit 1")
214                 if self.concernedRows==None:
215                         _moduleLogger.info("check4commit Updatung concernedRows")
216                         sql="SELECT pcdatum,rowid FROM logtable WHERE pcdatum>? ORDER BY pcdatum DESC"
217                         self.concernedRows=self.db.ladeSQL(sql,(lastdate,))
218
219                 if (self.concernedRows!=None)and(len(self.concernedRows)>0):
220                         #_moduleLogger.info("check4commit 2")
221                         id1, pcdatum,sql, param, host, rowid = newSQL
222
223                         if len(rowid)>0:
224                                 for x in self.concernedRows:
225                                         #_moduleLogger.info("check4commit 3")
226                                         if (x[1]==rowid):
227                                                 if (x[0]>pcdatum):
228                                                         _moduleLogger.info("newer sync entry, ignoring old one")
229                                                         #_moduleLogger.info("check4commit 9.1")
230                                                         return False
231                                                 else:
232                                                         #_moduleLogger.info("check4commit 9.2")
233                                                         return True
234
235                 #_moduleLogger.info("check4commit 9.3")
236                 return True
237
238         def writeSQLTupel(self,newSQLs,lastdate):
239                 if (newSQLs==None):
240                          return
241
242                 self.concernedRows=None
243                 pausenzaehler=0
244                 _moduleLogger.info("writeSQLTupel got "+str(len(newSQLs))+" sql tupels")
245                 for newSQL in newSQLs:
246                         #print ""
247                         #print "SQL1: ",newSQL[1]
248                         #print "SQL2: ",newSQL[2]
249                         #print "SQL3: ",newSQL[3]
250                         #print "Param:",string.split(newSQL[3]," <<Tren-ner>> ")
251                         #print ""
252                         if (newSQL[3]!=""):
253                                 param=string.split(newSQL[3]," <<Tren-ner>> ")
254                         else:
255                                 param=None
256
257                         if (len(newSQL)>2):
258                                 commitSQL=True
259
260                                 if (newSQL[5]!=None)and(len(newSQL[5])>0):
261                                         commitSQL=self.check4commit(newSQL,lastdate)
262
263                                 if (commitSQL==True):
264                                         self.db.speichereSQL(newSQL[2],param,commit=False,pcdatum=newSQL[1],rowid=newSQL[5])
265                         else:
266                                 _moduleLogger.error("writeSQLTupel: Error")
267
268                         pausenzaehler+=1
269                         if (pausenzaehler % 10)==0:
270                                 self.pulse()
271                                 while (gtk.events_pending()):
272                                         gtk.main_iteration();
273
274                 _moduleLogger.info("Alle SQLs an sqlite geschickt, commiting now")
275                 self.db.commitSQL()
276                 _moduleLogger.info("Alle SQLs commited")
277
278         def doSync(self,sync_uuid,pcdatum,newSQLs,pcdatumjetzt):
279                 #print uuid,pcdatum,newSQLs
280                 #_moduleLogger.info("doSync 0")
281                 self.changeSyncStatus(True,_("sync process running"))
282                 self.pulse()
283                 #_moduleLogger.info("doSync 1")
284
285                 while (gtk.events_pending()):
286                         gtk.main_iteration();
287                 diff=time.time()-pcdatumjetzt
288                 if diff<0:
289                         diff=diff*(-1)
290                 if diff>30:
291                         return -1
292
293                 _moduleLogger.info("doSync read sqls")
294                 sql="SELECT * FROM logtable WHERE pcdatum>?"
295                 rows=self.db.ladeSQL(sql,(pcdatum,))
296                 _moduleLogger.info("doSync read sqls")
297                 self.writeSQLTupel(newSQLs,pcdatum)
298                 _moduleLogger.info("doSync wrote "+str(len(newSQLs))+" sqls")
299                 _moduleLogger.info("doSync sending "+str(len(rows))+" sqls")
300                 i=0
301                 return rows
302
303         def getRemoteSyncUUID(self):
304                 return self.sync_uuid
305
306         def startServer(self, widget, data=None):
307                 #Starte RPCServer
308                 self.db.speichereDirekt("syncServerIP",self.comboIP.get_child().get_text())
309
310                 if (widget.get_active()==True):
311                         _moduleLogger.info("Starting Server")
312
313                         try:
314                                 ip=self.comboIP.get_child().get_text()
315                                 self.rpcserver = SimpleXMLRPCServer((ip, self.port),allow_none=True)
316                                 self.rpcserver.register_function(pow)
317                                 self.rpcserver.register_function(self.getLastSyncDate)
318                                 self.rpcserver.register_function(self.doSync)
319                                 self.rpcserver.register_function(self.getRemoteSyncUUID)
320                                 self.rpcserver.register_function(self.doSaveFinalTime)
321                                 self.rpcserver.register_function(self.pulse)
322                                 self.poll=select.poll()
323                                 self.poll.register(self.rpcserver.fileno())
324                                 gobject.timeout_add(1000, self.handleRPC)
325                                 self.syncServerStatusLabel.set_text(_("Syncserver running..."))
326
327                                 #save
328                                 self.db.speichereDirekt("startSyncServer",True)
329
330                         except:
331                                 s=str(sys.exc_info())
332                                 _moduleLogger.error("libsync: could not start server. Error: "+s)
333                                 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
334                                 mbox.set_modal(False)
335                                 response=mbox.run()
336                                 mbox.hide()
337                                 mbox.destroy()
338                                 widget.set_active(False)
339                 else:
340                         _moduleLogger.info("Stopping Server")
341                         try:
342                                 del self.rpcserver
343                         except:
344                                 pass
345                         self.syncServerStatusLabel.set_text(_("Syncserver not running..."))
346                         #save
347                         self.db.speichereDirekt("startSyncServer",False)
348
349         def doSaveFinalTime(self,sync_uuid,pcdatum=None):
350                 if (pcdatum==None): pcdatum=int(time.time())
351                 if (time.time()>pcdatum):
352                         pcdatum=int(time.time()) #größere Zeit nehmen
353
354                 self.pulse()
355
356                 #fime save time+uuid
357                 sql="DELETE FROM sync WHERE uuid=?"
358                 self.db.speichereSQL(sql,(sync_uuid,),log=False)
359                 sql="INSERT INTO sync (syncpartner,uuid,pcdatum) VALUES (?,?,?)"
360                 self.db.speichereSQL(sql,("x",str(sync_uuid),pcdatum),log=False)
361                 self.pulse()
362                 self.changeSyncStatus(False,_("no sync process (at the moment)"))
363                 return (self.sync_uuid,pcdatum)
364
365         def syncButton(self, widget, data=None):
366                 _moduleLogger.info("Syncing")
367                 #sql="DELETE FROM logtable WHERE sql LIKE externeStundenplanung"
368                 #self.db.speichereSQL(sql)
369
370                 self.changeSyncStatus(True,_("sync process running"))
371                 while (gtk.events_pending()):
372                         gtk.main_iteration();
373
374                 self.db.speichereDirekt("syncRemoteIP",self.comboRemoteIP.get_child().get_text())
375                 try:
376                         self.server = xmlrpclib.ServerProxy("http://"+self.comboRemoteIP.get_child().get_text()+":"+str(self.port),allow_none=True)
377                         #lastDate=server.getLastSyncDate(str(self.sync_uuid))
378                         server_sync_uuid=self.server.getRemoteSyncUUID()
379                         lastDate=self.getLastSyncDate(str(server_sync_uuid))
380
381                         #print ("LastSyncDate: "+str(lastDate)+" Now: "+str(int(time.time())))
382
383                         sql="SELECT * FROM logtable WHERE pcdatum>?"
384                         rows=self.db.ladeSQL(sql,(lastDate,))
385
386                         _moduleLogger.info("loaded concerned rows")
387
388                         newSQLs=self.server.doSync(self.sync_uuid,lastDate,rows,time.time())
389
390                         _moduleLogger.info("did do sync, processing sqls now")
391                         if newSQLs!=-1:
392                                 self.writeSQLTupel(newSQLs,lastDate)
393
394                                 sync_uuid, finalpcdatum=self.server.doSaveFinalTime(self.sync_uuid)
395                                 self.doSaveFinalTime(sync_uuid, finalpcdatum)
396
397                                 self.changeSyncStatus(False,_("no sync process (at the moment)"))
398
399                                 mbox =  gtk.MessageDialog(None,gtk.DIALOG_MODAL,gtk.MESSAGE_INFO,gtk.BUTTONS_OK,_("Synchronization successfully completed"))
400                                 response = mbox.run()
401                                 mbox.hide()
402                                 mbox.destroy()
403                         else:
404                                 _moduleLogger.warning("Zeitdiff zu groß/oder anderer db-Fehler")
405                                 self.changeSyncStatus(False,_("no sync process (at the moment)"))
406                                 mbox =  gtk.MessageDialog(None,gtk.DIALOG_MODAL,gtk.MESSAGE_INFO,gtk.BUTTONS_OK,_("The clocks are not synchronized between stations"))
407                                 response = mbox.run()
408                                 mbox.hide()
409                                 mbox.destroy()
410                 except:
411                                 _moduleLogger.warning("Sync connect failed")
412                                 self.changeSyncStatus(False,_("no sync process (at the moment)"))
413                                 mbox =  gtk.MessageDialog(None,gtk.DIALOG_MODAL,gtk.MESSAGE_INFO,gtk.BUTTONS_OK,_("Sync failed, reason: ")+unicode(sys.exc_info()[1][1]))
414                                 response = mbox.run()
415                                 mbox.hide()
416                                 mbox.destroy()
417                                 self.server=None
418                 self.server=None