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