a469595b2f74cb2f38219bd1448a8049214c17ed
[cs-p2p-next.git] / ppf / new / storage.py
1 """
2 Storage class for P2P logging information.
3
4 Built on previous work by Adriana Draghici and Razvan Deaconescu.
5
6 2011, Razvan Deaconescu, razvan.deaconescu@cs.pub.ro
7 2011, Mariana Marasoiu, mariana.marasoiu@gmail.com
8 """
9
10 import os
11 import os.path
12 import re
13 import logging
14 import sqlite3
15 import MySQLdb
16 import datetime
17
18 #
19 # Logging code heavily inspired by Logging HOWTO documentation:
20 #     http://docs.python.org/dev/howto/logging.html#configuring-logging
21 #
22
23 # Create logger; default logging level is DEBUG.
24 logger = logging.getLogger(__name__)
25 logger.setLevel(logging.DEBUG)
26
27 # Create console handler and set level to ERROR.
28 ch = logging.StreamHandler()
29 ch.setLevel(logging.DEBUG)
30
31 # Create formatter.
32 formatter = logging.Formatter('%(filename)s:%(lineno)s - '
33                               '%(levelname)s: %(message)s')
34
35 # Add formatter to console handler.
36 ch.setFormatter(formatter)
37
38 # Add console handler to logger.
39 logger.addHandler(ch)
40
41
42 message_types = {
43         'CHOKE': {'id': 1, 'parameters': None},
44         'UNCHOKE': {'id': 2, 'parameters': None},
45         'INTERESTED': {'id': 3, 'parameters': None},
46         'NOT_INTERESTED': {'id': 4, 'parameters': None},
47         'HAVE': {'id': 5, 'parameters': None},
48         'BITFIELD': {'id': 6, 'parameters': None},
49         'REQUEST': {'id': 7, 'parameters': None},
50         'PIECE': {'id': 8, 'parameters': None},
51         'CANCEL': {'id': 9, 'parameters': None},
52         'DHT_PORT': {'id': 10, 'parameters': None}
53 }
54
55 bittorrent_clients = {
56         'Tribler': {
57             'id': 1,
58             'language': 'Python',
59             'url': 'http://www.tribler.org/trac',
60             'dht_support': True,
61             'streaming_support': True,
62             'pxe_support': None,
63             'features': None
64         },
65         'NextShare': {
66             'id': 2,
67             'language': 'Python',
68             'url': 'https://trac.p2p-next.org/',
69             'dht_support': True,
70             'streaming_support': True,
71             'pxe_support': None,
72             'features': None
73         },
74         'libtorrent-rasterbar': {
75             'id': 3,
76             'language': 'C++',
77             'url': 'http://www.rasterbar.com/products/libtorrent/',
78             'dht_support': True,
79             'streaming_support': True,
80             'pxe_support': None,
81             'features': None
82         },
83         'Vuze': {
84             'id': 4,
85             'language': 'Java',
86             'url': 'http://www.vuze.com/',
87             'dht_support': True,
88             'streaming_support': True,
89             'pxe_support': None,
90             'features': None
91         },
92         'Transmission': {
93             'id': 5,
94             'language': 'C',
95             'url': 'http://www.transmissionbt.com/',
96             'dht_support': True,
97             'streaming_support': False,
98             'pxe_support': None,
99             'features': None
100         },
101         'Aria': {
102             'id': 6,
103             'language': 'C',
104             'url': 'http://aria2.sourceforge.net/',
105             'dht_support': True,
106             'streaming_support': False,
107             'pxe_support': None,
108             'features': None
109         },
110         'Mainline': {
111             'id': 7,
112             'language': 'Python',
113             'url': 'http://www.bittorrent.com/',
114             'dht_support': True,
115             'streaming_support': False,
116             'pxe_support': None,
117             'features': None
118         }
119 }
120
121 transfer_directions = {
122         'receive': 1,
123         'send': 2
124 }
125
126 class Swarm(object):
127     """ Class mimics a C structure. """
128     def __init__(self, torrent_filename=None, data_size=None,
129             description=None):
130         self.torrent_filename = torrent_filename
131         self.data_size = data_size
132         self.description = description
133
134 class ClientSession(object):
135     """ Class mimics a C structure. """
136     # TODO: Add timezone.
137     def __init__(self, swarm_id=None, btclient=None, system_os=None,
138             system_os_version=None, system_ram=None, system_cpu=None,
139             public_ip=None, public_port=None, ds_limit=None, us_limit=None,
140             start_time=None, dht_enabled=None, pxe_enabled=None,
141             streaming_enabled=None, features=None, description=None):
142         self.swarm_id = swarm_id
143         self.btclient = btclient
144         self.system_os = system_os
145         self.system_os_version = system_os_version
146         self.system_ram = system_ram
147         self.system_cpu = system_cpu
148         self.public_ip = public_ip
149         self.public_port = public_port
150         self.ds_limit = ds_limit
151         self.us_limit = us_limit
152         self.start_time = start_time
153         self.dht_enabled = dht_enabled
154         self.pxe_enabled = pxe_enabled
155         self.streaming_enabled = streaming_enabled
156         self.features = features
157         self.description = description
158
159 class PeerStatusMessage(object):
160     """ Class mimics a C structure. """
161     def __init__(self, swarm_id=None, client_session_id=None, timestamp=None,
162             peer_ip=None, peer_port=None, download_speed=None,
163             upload_speed=None):
164         self.swarm_id = swarm_id
165         self.client_session_id = client_session_id
166         self.timestamp = timestamp
167         self.peer_ip = peer_ip
168         self.peer_port = peer_port
169         self.download_speed = download_speed
170         self.upload_speed = upload_speed
171
172 class StatusMessage(object):
173     """ Class mimics a C structure. """
174     def __init__(self, swarm_id=None, client_session_id=None, timestamp=None,
175             time=None, num_peers=None, num_dht_peers=None,
176             download_speed=None, upload_speed=None, download_size=None,
177             upload_size=None, eta=None):
178         self.swarm_id = swarm_id
179         self.client_session_id = client_session_id
180         self.timestamp = timestamp
181         self.num_peers = num_peers
182         self.num_dht_peers = num_dht_peers
183         self.download_speed = download_speed
184         self.upload_speed = upload_speed
185         self.download_size = download_size
186         self.upload_size = upload_size
187         self.eta = eta
188
189 class VerboseMessage(object):
190     """ Class mimics a C structure. """
191     def __init__(self, swarm_id=None, client_session_id=None, timestamp=None,
192             transfer_direction=None, peer_ip=None, peer_port=None,
193             message_type=None, index=None, begin=None, length=None,
194             listen_port=None):
195         self.swarm_id = swarm_id
196         self.client_session_id = client_session_id
197         self.timestamp = timestamp
198         self.transfer_direction = transfer_direction
199         self.peer_ip = peer_ip
200         self.peer_port = peer_port
201         self.message_type = message_type
202         self.index = index
203         self.begin = begin
204         self.length = length
205         self.listen_port = listen_port
206
207
208 class SwarmDataAccess(object):
209
210     """Base class for swarm information storage.
211
212     Define main operations on storage objects: Swarm, ClientSession,
213     StatusMessage, PeerStatusMessage, VerboseMessage.
214     Each object has four corresponding operations: add, remove, get, update.
215     """
216
217     def __init__(self):
218         pass
219
220     def add_swarm(self, swarm):
221         pass
222
223     def remove_swarm(self):
224         pass
225
226     def get_swarm(self):
227         pass
228
229     def update_swarm(self):
230         pass
231
232     def add_client_session(self, session):
233         pass
234
235     def remove_client_session(self):
236         pass
237
238     def get_client_session(self):
239         pass
240
241     def update_client_session(self):
242         pass
243
244     def add_peer_status_message(self, msg):
245         pass
246
247     def remove_peer_status_message(self):
248         pass
249
250     def get_peer_status_message(self):
251         pass
252
253     def update_peer_status_message(self):
254         pass
255
256     def add_status_message(self, msg):
257         pass
258
259     def remove_status_message(self):
260         pass
261
262     def get_status_message(self):
263         pass
264
265     def update_status_message(self):
266         pass
267
268     def add_verbose_message(self, msg):
269         pass
270
271     def remove_verbose_message(self):
272         pass
273
274     def get_verbose_message(self):
275         pass
276
277     def update_verbose_message(self):
278         pass
279
280
281 class FileAccess(SwarmDataAccess):
282
283     """Subclass of SwarmDataAccess providing access for file storage."""
284
285     def __init__(self, path):
286         """Add base_path attribute received as argument."""
287         self.base_path = path
288
289
290 def find_last_numeric_subfolder(path):
291     """Find last numeric folder in base_path folder.
292
293     The last numeric folder is the last swarm_id.
294     """
295
296     dir_list = []
297     pattern = re.compile("[0-9]+")
298
299     # Browse entries in base_path folder.
300     listing = os.listdir(path)
301     for entry in listing:
302         # If directory name is a number (id) add it to the list.
303         if os.path.isdir(os.path.join(path, entry)):
304             if pattern.match(entry):
305                 dir_list.append(int(entry))
306
307     if not dir_list:
308         return None
309     else:
310         dir_list.sort()
311         return dir_list[len(dir_list)-1]
312
313
314 class TreeTextFileAccess(FileAccess):
315
316     """Child class of FileAccess for directory tree structure access."""
317
318     def __init__(self, path):
319         """Use superclass method for initialisation of constructor."""
320         super(TreeTextFileAccess, self).__init__(path)
321
322     def add_swarm(self, swarm):
323         """
324         Create a subfolder with an unique id. Add 1 to the last numeric
325         subfolder id. In case none exists, use 1 as id.
326         """
327         id = find_last_numeric_subfolder(self.base_path)
328         if id == None:
329             id = 1
330         else:
331             id = id+1
332
333         swarm_path = os.path.join(self.base_path, str(id))
334         os.mkdir(swarm_path)
335
336         swarm_config = os.path.join(swarm_path, "swarm.conf")
337         f = open(swarm_config, 'w')
338         f.write("""id = %s
339         torrent_filename = %s
340         data_size = %s
341         description = %s
342         """ %(id, swarm.torrent_filename, swarm.data_size, swarm.description))
343         f.close()
344
345     def add_client_session(self, session):
346         """
347         Create session subfolder in swarm subfolder and add config file.
348         TODO: Throw exception in case swarm subfolder doesn't exist.
349         """
350         swarm_path = os.path.join(self.base_path, str(session.swarm_id))
351
352         # Search first available folder in swarm_path.
353         id = find_last_numeric_subfolder(swarm_path)
354         if id == None:
355             id = 1
356         else:
357             id = id+1
358
359         # Create session subfolder.
360         session_path = os.path.join(swarm_path, str(id))
361         os.mkdir(session_path)
362
363         # Create and populate configuration file.
364         session_config = os.path.join(session_path, "client_session.conf")
365         f = open(session_config, 'w')
366         f.write("""id = %s
367         swarm_id = %s
368         btclient = %s
369         system_os = %s
370         system_os_version = %s
371         system_ram = %s
372         system_cpu = %s
373         public_ip = %s
374         public_port = %s
375         ds_limit = %s
376         us_limit = %s
377         start_time = %s
378         dht_enabled = %s
379         pxe_enabled = %s
380         streaming_enabled = %s
381         features = %s
382         description = %s
383         """ %(id, session.swarm_id, session.btclient, session.system_os,
384             session.system_os_version, session.system_ram, session.system_cpu,
385             session.public_ip, session.public_port, session.ds_limit,
386             session.us_limit, session.start_time, session.dht_enabled,
387             session.pxe_enabled, session.streaming_enabled,
388             session.features, session.description))
389         f.close()
390
391     def add_peer_status_message(self, msg):
392         """Add peer status msg line to file. msg type is PeerStatusMessage."""
393         # TODO: id is number of lines in file.
394         swarm_path = os.path.join(self.base_path, str(msg.swarm_id))
395         session_path = os.path.join(swarm_path, str(msg.client_session_id))
396         message_file = os.path.join(session_path, "peer_status.txt")
397
398         f = open(message_file, 'a')
399         f.write("""%s,%s,%s,%s,%s,%s,%s,%s\n"""
400                 %(1, msg.swarm_id, msg.client_session_id, msg.timestamp,
401                     msg.peer_ip, msg.peer_port, msg.download_speed,
402                     msg.upload_speed))
403         f.close()
404
405     def add_status_message(self, msg):
406         """Add status msg line to file. msg type is StatusMessage."""
407         # TODO: id is number of lines in file.
408         swarm_path = os.path.join(self.base_path, str(msg.swarm_id))
409         session_path = os.path.join(swarm_path, str(msg.client_session_id))
410         message_file = os.path.join(session_path, "status.txt")
411
412         f = open(message_file, 'a')
413         f.write("""%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n"""
414                 %(1, msg.swarm_id, msg.client_session_id, msg.timestamp,
415                     msg.num_peers, msg.num_dht_peers, msg.download_speed,
416                     msg.upload_speed, msg.download_size, msg.upload_size,
417                     msg.eta))
418         f.close()
419
420     def add_verbose_message(self, msg):
421         """Add verbose msg line to file. msg type is VerboseMessage."""
422         # TODO: id is number of lines in file.
423         swarm_path = os.path.join(self.base_path, str(msg.swarm_id))
424         session_path = os.path.join(swarm_path, str(msg.client_session_id))
425         message_file = os.path.join(session_path, "verbose.txt")
426
427         f = open(message_file, 'a')
428         f.write("""%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n"""
429                 %(1, msg.swarm_id, msg.client_session_id, msg.timestamp,
430                     msg.transfer_direction, msg.peer_ip, msg.peer_port,
431                     msg.message_type, msg.index, msg.begin, msg.length,
432                     msg.listen_port))
433         f.close()
434
435 class DatabaseAccess(SwarmDataAccess):
436
437     """Subclass of SwarmDataAccess providing functions for database usage."""
438
439     def __init__(self, database):
440         """Add database attribute received as argument.
441
442         The argument may be a name or a list with options for connection.
443         """
444
445         self.database = database
446
447     def connect(self):
448         """Initialize access attributes."""
449         self.conn = None
450         self.cursor = None
451
452     def disconnect(self):
453         """Close connection with database."""
454         self.cursor.close()
455         self.conn.close()
456
457 class SQLiteDatabaseAccess(DatabaseAccess):
458
459     """Child class of DatabaseAccess for SQLite access."""
460
461     def __init__(self, database):
462         """Use superclass method for initialisation of constructor."""
463         super(SQLiteDatabaseAccess, self).__init__(database)
464
465     def connect(self):
466         """Connect to sqlite3 database."""
467         self.conn = sqlite3.connect(self.database)
468         self.cursor = self.conn.cursor()
469         # Use foreign key support if available.
470         self.cursor.execute("PRAGMA foreign_keys = ON")
471         self.conn.commit()
472
473     def reset_query(self):
474         self.columns = ""
475         self.values = ""
476
477     def append_to_insert_query(self, data_name, data_value):
478         if data_value is not None:
479             self.columns = self.columns + data_name + ", "
480             self.values = self.values + "'" + str(data_value) + "'" + ", "
481
482     def add_swarm(self, swarm):
483         """Insert swarm in database. swarm type is Swarm."""
484         self.reset_query()
485         self.append_to_insert_query("torrent_filename", swarm.torrent_filename)
486         self.append_to_insert_query("data_size", swarm.data_size)
487         self.append_to_insert_query("description", swarm.description)
488
489         self.columns = re.sub(',\s*$', '', self.columns)
490         self.values = re.sub(',\s*$', '', self.values)
491         insert_query = "INSERT INTO swarms(" + self.columns +")" + \
492                 " VALUES(" + self.values + ")"
493         self.cursor.execute(insert_query)
494         self.conn.commit()
495
496     def add_client_session(self, session):
497         """Insert session in database. session type is Session."""
498         self.reset_query()
499         self.append_to_insert_query("swarm_id", session.swarm_id)
500         # TODO: search database for client ID
501         self.append_to_insert_query("btclient_id",
502                 bittorrent_clients[session.btclient]['id'])
503         self.append_to_insert_query("system_os", session.system_os)
504         self.append_to_insert_query("system_os_version",
505                                     session.system_os_version)
506         self.append_to_insert_query("system_ram", session.system_ram)
507         self.append_to_insert_query("system_cpu", session.system_cpu)
508         self.append_to_insert_query("public_ip", session.public_ip)
509         self.append_to_insert_query("public_port", session.public_port)
510         self.append_to_insert_query("ds_limit", session.ds_limit)
511         self.append_to_insert_query("us_limit", session.us_limit)
512         self.append_to_insert_query("start_time", session.start_time)
513         self.append_to_insert_query("dht_enabled", session.dht_enabled)
514         self.append_to_insert_query("pxe_enabled", session.pxe_enabled)
515         self.append_to_insert_query("streaming_enabled",
516                                     session.streaming_enabled)
517         self.append_to_insert_query("features", session.features)
518         self.append_to_insert_query("description", session.description)
519
520         self.columns = re.sub(',\s*$', '', self.columns)
521         self.values = re.sub(',\s*$', '', self.values)
522         insert_query = "INSERT INTO client_sessions(" + self.columns +")" + \
523                 " VALUES(" + self.values + ")"
524         self.cursor.execute(insert_query)
525         self.conn.commit()
526
527     def get_string_timestamp(self, ts):
528         # Timestamp is Python Datatime. Convert it to string format and
529         # pass it to internal SQLITE julianday() function.
530         return "%s-%s-%s %s:%s:%s.%s" \
531                 %(ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second,
532                         1000 * ts.microsecond)
533
534     def add_peer_status_message(self, msg):
535         """Insert peer status message in database.
536         msg type is PeerStatusMessage.
537         """
538         self.reset_query()
539         self.append_to_insert_query("client_session_id", msg.client_session_id)
540
541         # TODO: Check msg.timestamp is not None. Raise exception.
542         timestamp_string = self.get_string_timestamp(msg.timestamp)
543         value = "julianday(" + timestamp_string + ")"
544         self.append_to_insert_query("timestamp", value)
545
546         self.append_to_insert_query("timestamp", msg.timestamp)
547         self.append_to_insert_query("peer_ip", msg.peer_ip)
548         self.append_to_insert_query("peer_port", msg.peer_port)
549         self.append_to_insert_query("download_speed", msg.download_speed)
550         self.append_to_insert_query("upload_speed", msg.upload_speed)
551
552         self.columns = re.sub(',\s*$', '', self.columns)
553         self.values = re.sub(',\s*$', '', self.values)
554         insert_query = "INSERT INTO peer_status_messages(" + \
555                 self.columns +")" + " VALUES(" + self.values + ")"
556         self.cursor.execute(insert_query)
557         self.conn.commit()
558
559     def add_status_message(self, msg):
560         """Insert status msg in database. msg type is StatusMessage."""
561         self.reset_query()
562         self.append_to_insert_query("client_session_id", msg.client_session_id)
563
564         # TODO: Check msg.timestamp is not None. Raise exception.
565         timestamp_string = self.get_string_timestamp(msg.timestamp)
566         value = "julianday(" + timestamp_string + ")"
567         self.append_to_insert_query("timestamp", value)
568
569         self.append_to_insert_query("num_peers", msg.num_peers)
570         self.append_to_insert_query("num_dht_peers", msg.num_dht_peers)
571         self.append_to_insert_query("download_speed", msg.download_speed)
572         self.append_to_insert_query("upload_speed", msg.upload_speed)
573         self.append_to_insert_query("download_size", msg.download_size)
574         self.append_to_insert_query("upload_size", msg.upload_size)
575         self.append_to_insert_query("eta", msg.eta)
576
577         self.columns = re.sub(',\s*$', '', self.columns)
578         self.values = re.sub(',\s*$', '', self.values)
579         insert_query = "INSERT INTO status_messages(" + self.columns +")" + \
580                 " VALUES(" + self.values + ")"
581         self.cursor.execute(insert_query)
582         self.conn.commit()
583
584     def add_verbose_message(self, msg):
585         """Insert verbose msg in database. msg type is VerboseMessage."""
586         self.reset_query()
587         self.append_to_insert_query("client_session_id", msg.client_session_id)
588
589         # TODO: Check msg.timestamp is not None. Raise exception.
590         timestamp_string = self.get_string_timestamp(msg.timestamp)
591         value = "julianday(" + timestamp_string + ")"
592         self.append_to_insert_query("timestamp", value)
593
594         self.append_to_insert_query("transfer_direction_id",
595                 transfer_directions[msg.transfer_direction])
596         self.append_to_insert_query("peer_ip", msg.peer_ip)
597         self.append_to_insert_query("peer_port", msg.peer_port)
598         self.append_to_insert_query("message_type_id",
599                 message_types[msg.message_type]['id'])
600         self.append_to_insert_query("index", msg.index)
601         self.append_to_insert_query("begin", msg.begin)
602         self.append_to_insert_query("length", msg.length)
603         self.append_to_insert_query("listen_port", msg.listen_port)
604
605         self.columns = re.sub(',\s*$', '', self.columns)
606         self.values = re.sub(',\s*$', '', self.values)
607         insert_query = "INSERT INTO verbose_messages(" + self.columns +")" + \
608                 " VALUES(" + self.values + ")"
609         self.cursor.execute(insert_query)
610         self.conn.commit()
611
612 class MySQLDatabaseAccess(DatabaseAccess):
613
614     """Child class of DatabaseAccess for MySQL access."""
615
616     def __init__(self, database):
617         """Initialize the database attribute of the class.
618
619         Use superclass method for initialisation of constructor.
620         """
621
622         super(MySQLDatabaseAccess, self).__init__(database)
623
624     def connect(self):
625         """Connect to MySQL database."""
626         self.conn = MySQLdb.Connection(db=self.database['database'],
627                                        user=self.database['user'],
628                                        passwd=self.database['password'],
629                                        host=self.database['host'],
630                                        port=int(self.database['port']))
631         self.cursor = self.conn.cursor()
632         self.conn.commit()
633
634     def reset_query(self):
635         self.columns = ""
636         self.values = ""
637
638     def append_to_insert_query(self, data_name, data_value):
639         if data_value is not None:
640             self.columns = self.columns + data_name + ", "
641             self.values = self.values + "'" + str(data_value) + "'" + ", "
642
643     def add_swarm(self, swarm):
644         """Insert swarm in database. swarm type is Swarm."""
645         self.reset_query()
646         self.append_to_insert_query("torrent_filename", swarm.torrent_filename)
647         self.append_to_insert_query("data_size", swarm.data_size)
648         self.append_to_insert_query("description", swarm.description)
649
650         self.columns = re.sub(',\s*$', '', self.columns)
651         self.values = re.sub(',\s*$', '', self.values)
652         insert_query = "INSERT INTO swarms(" + self.columns +")" + \
653                 " VALUES(" + self.values + ")"
654         self.cursor.execute(insert_query)
655         self.conn.commit()
656
657     def add_client_session(self, session):
658         """Insert session in database. session type is Session."""
659         self.reset_query()
660         self.append_to_insert_query("swarm_id", session.swarm_id)
661         # TODO: search database for client ID
662         self.append_to_insert_query("btclient_id",
663                 bittorrent_clients[session.btclient]['id'])
664         self.append_to_insert_query("system_os", session.system_os)
665         self.append_to_insert_query("system_os_version",
666                                     session.system_os_version)
667         self.append_to_insert_query("system_ram", session.system_ram)
668         self.append_to_insert_query("system_cpu", session.system_cpu)
669         self.append_to_insert_query("public_ip", session.public_ip)
670         self.append_to_insert_query("public_port", session.public_port)
671         self.append_to_insert_query("ds_limit", session.ds_limit)
672         self.append_to_insert_query("us_limit", session.us_limit)
673         self.append_to_insert_query("start_time", session.start_time)
674         self.append_to_insert_query("dht_enabled", session.dht_enabled)
675         self.append_to_insert_query("pxe_enabled", session.pxe_enabled)
676         self.append_to_insert_query("streaming_enabled",
677                                     session.streaming_enabled)
678         self.append_to_insert_query("features", session.features)
679         self.append_to_insert_query("description", session.description)
680
681         self.columns = re.sub(',\s*$', '', self.columns)
682         self.values = re.sub(',\s*$', '', self.values)
683         insert_query = "INSERT INTO client_sessions(" + self.columns +")" + \
684                 " VALUES(" + self.values + ")"
685         self.cursor.execute(insert_query)
686         self.conn.commit()
687
688     def get_string_timestamp(self, ts):
689         # Timestamp is Python Datatime. Convert it to string format.
690         return "%s-%s-%s %s:%s:%s" \
691                 %(ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second)
692
693     def add_peer_status_message(self, msg):
694         """Insert peer status message in database.
695         msg type is PeerStatusMessage.
696         """
697         self.reset_query()
698         self.append_to_insert_query("client_session_id", msg.client_session_id)
699
700         # TODO: Check msg.timestamp is not None. Raise exception.
701         timestamp_string = self.get_string_timestamp(msg.timestamp)
702
703         self.append_to_insert_query("timestamp", timestamp_string)
704         self.append_to_insert_query("peer_ip", msg.peer_ip)
705         self.append_to_insert_query("peer_port", msg.peer_port)
706         self.append_to_insert_query("download_speed", msg.download_speed)
707         self.append_to_insert_query("upload_speed", msg.upload_speed)
708
709         self.columns = re.sub(',\s*$', '', self.columns)
710         self.values = re.sub(',\s*$', '', self.values)
711         insert_query = "INSERT INTO peer_status_messages(" + \
712                 self.columns +")" + " VALUES(" + self.values + ")"
713         self.cursor.execute(insert_query)
714         self.conn.commit()
715
716     def add_status_message(self, msg):
717         """Insert status msg in database. msg type is StatusMessage."""
718         self.reset_query()
719         self.append_to_insert_query("client_session_id", msg.client_session_id)
720
721         # TODO: Check msg.timestamp is not None. Raise exception.
722         timestamp_string = self.get_string_timestamp(msg.timestamp)
723
724         self.append_to_insert_query("timestamp", timestamp_string)
725         self.append_to_insert_query("num_peers", msg.num_peers)
726         self.append_to_insert_query("num_dht_peers", msg.num_dht_peers)
727         self.append_to_insert_query("download_speed", msg.download_speed)
728         self.append_to_insert_query("upload_speed", msg.upload_speed)
729         self.append_to_insert_query("download_size", msg.download_size)
730         self.append_to_insert_query("upload_size", msg.upload_size)
731         self.append_to_insert_query("eta", msg.eta)
732
733         self.columns = re.sub(',\s*$', '', self.columns)
734         self.values = re.sub(',\s*$', '', self.values)
735         insert_query = "INSERT INTO status_messages(" + self.columns +")" + \
736                 " VALUES(" + self.values + ")"
737         self.cursor.execute(insert_query)
738         self.conn.commit()
739
740     def add_verbose_message(self, msg):
741         """Insert verbose msg in database. msg type is VerboseMessage."""
742         self.reset_query()
743         self.append_to_insert_query("client_session_id", msg.client_session_id)
744
745         # TODO: Check msg.timestamp is not None. Raise exception.
746         timestamp_string = self.get_string_timestamp(msg.timestamp)
747
748         self.append_to_insert_query("timestamp", timestamp_string)
749         self.append_to_insert_query("transfer_direction_id",
750                 transfer_directions[msg.transfer_direction])
751         self.append_to_insert_query("peer_ip", msg.peer_ip)
752         self.append_to_insert_query("peer_port", msg.peer_port)
753         self.append_to_insert_query("message_type_id",
754                 message_types[msg.message_type]['id'])
755         self.append_to_insert_query("index", msg.index)
756         self.append_to_insert_query("begin", msg.begin)
757         self.append_to_insert_query("length", msg.length)
758         self.append_to_insert_query("listen_port", msg.listen_port)
759
760         self.columns = re.sub(',\s*$', '', self.columns)
761         self.values = re.sub(',\s*$', '', self.values)
762         insert_query = "INSERT INTO verbose_messages(" + self.columns +")" + \
763                 " VALUES(" + self.values + ")"
764         self.cursor.execute(insert_query)
765         self.conn.commit()
766
767 class SwarmWriter(object):
768     """Wrapper class for swarm storage write actions.
769
770     Multiple *Access objects may be added to the clas resulting in multiple
771     storage types.
772     For example, adding a swarm could result in
773        * adding a table entry in a MySQL database (MySQLDatabaseAccess)
774        * adding a table entry in an SQLite database (SQLiteDatabaseAccess)
775        * adding a new folder in a tree structure (TreeTextFileAccess)
776     """
777
778     def __init__(self):
779         self.handlers = []
780
781     def add_access_handle(self, handle):
782         self.handlers.append(handle)
783
784     def remove_access_handle(self, handle):
785         self.handlers.remove(handle)
786
787     def add_swarm(self, swarm):
788         for h in self.handlers:
789             h.add_swarm(swarm)
790
791     def add_client_session(self, session):
792         for h in self.handlers:
793             h.add_client_session(session)
794
795     def add_peer_status_message(self, msg):
796         for h in self.handlers:
797             h.add_peer_status_message(msg)
798
799     def add_status_message(self, msg):
800         for h in self.handlers:
801             h.add_status_message(msg)
802
803     def add_verbose_message(self, msg):
804         for h in self.handlers:
805             h.add_verbose_message(msg)