2 Storage class for P2P logging information.
4 Built on previous work by Adriana Draghici and Razvan Deaconescu.
6 2011, Razvan Deaconescu, razvan.deaconescu@cs.pub.ro
7 2011, Mariana Marasoiu, mariana.marasoiu@gmail.com
19 # Logging code heavily inspired by Logging HOWTO documentation:
20 # http://docs.python.org/dev/howto/logging.html#configuring-logging
23 # Create logger; default logging level is DEBUG.
24 logger = logging.getLogger(__name__)
25 logger.setLevel(logging.DEBUG)
27 # Create console handler and set level to ERROR.
28 ch = logging.StreamHandler()
29 ch.setLevel(logging.DEBUG)
32 formatter = logging.Formatter('%(filename)s:%(lineno)s - '
33 '%(levelname)s: %(message)s')
35 # Add formatter to console handler.
36 ch.setFormatter(formatter)
38 # Add console handler to logger.
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}
55 bittorrent_clients = {
59 'url': 'http://www.tribler.org/trac',
61 'streaming_support': True,
68 'url': 'https://trac.p2p-next.org/',
70 'streaming_support': True,
74 'libtorrent-rasterbar': {
77 'url': 'http://www.rasterbar.com/products/libtorrent/',
79 'streaming_support': True,
86 'url': 'http://www.vuze.com/',
88 'streaming_support': True,
95 'url': 'http://www.transmissionbt.com/',
97 'streaming_support': False,
104 'url': 'http://aria2.sourceforge.net/',
106 'streaming_support': False,
112 'language': 'Python',
113 'url': 'http://www.bittorrent.com/',
115 'streaming_support': False,
121 transfer_directions = {
127 """ Class mimics a C structure. """
128 def __init__(self, torrent_filename=None, data_size=None,
130 self.torrent_filename = torrent_filename
131 self.data_size = data_size
132 self.description = description
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
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,
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
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
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,
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
205 self.listen_port = listen_port
208 class SwarmDataAccess(object):
210 """Base class for swarm information storage.
212 Define main operations on storage objects: Swarm, ClientSession,
213 StatusMessage, PeerStatusMessage, VerboseMessage.
214 Each object has four corresponding operations: add, remove, get, update.
220 def add_swarm(self, swarm):
223 def remove_swarm(self):
229 def update_swarm(self):
232 def add_client_session(self, session):
235 def remove_client_session(self):
238 def get_client_session(self):
241 def update_client_session(self):
244 def add_peer_status_message(self, msg):
247 def remove_peer_status_message(self):
250 def get_peer_status_message(self):
253 def update_peer_status_message(self):
256 def add_status_message(self, msg):
259 def remove_status_message(self):
262 def get_status_message(self):
265 def update_status_message(self):
268 def add_verbose_message(self, msg):
271 def remove_verbose_message(self):
274 def get_verbose_message(self):
277 def update_verbose_message(self):
281 class FileAccess(SwarmDataAccess):
283 """Subclass of SwarmDataAccess providing access for file storage."""
285 def __init__(self, path):
286 """Add base_path attribute received as argument."""
287 self.base_path = path
290 def find_last_numeric_subfolder(path):
291 """Find last numeric folder in base_path folder.
293 The last numeric folder is the last swarm_id.
297 pattern = re.compile("[0-9]+")
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))
311 return dir_list[len(dir_list)-1]
314 class TreeTextFileAccess(FileAccess):
316 """Child class of FileAccess for directory tree structure access."""
318 def __init__(self, path):
319 """Use superclass method for initialisation of constructor."""
320 super(TreeTextFileAccess, self).__init__(path)
322 def add_swarm(self, swarm):
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.
327 id = find_last_numeric_subfolder(self.base_path)
333 swarm_path = os.path.join(self.base_path, str(id))
336 swarm_config = os.path.join(swarm_path, "swarm.conf")
337 f = open(swarm_config, 'w')
339 torrent_filename = %s
342 """ %(id, swarm.torrent_filename, swarm.data_size, swarm.description))
345 def add_client_session(self, session):
347 Create session subfolder in swarm subfolder and add config file.
348 TODO: Throw exception in case swarm subfolder doesn't exist.
350 swarm_path = os.path.join(self.base_path, str(session.swarm_id))
352 # Search first available folder in swarm_path.
353 id = find_last_numeric_subfolder(swarm_path)
359 # Create session subfolder.
360 session_path = os.path.join(swarm_path, str(id))
361 os.mkdir(session_path)
363 # Create and populate configuration file.
364 session_config = os.path.join(session_path, "client_session.conf")
365 f = open(session_config, 'w')
370 system_os_version = %s
380 streaming_enabled = %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))
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")
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,
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")
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,
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")
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,
435 class DatabaseAccess(SwarmDataAccess):
437 """Subclass of SwarmDataAccess providing functions for database usage."""
439 def __init__(self, database):
440 """Add database attribute received as argument.
442 The argument may be a name or a list with options for connection.
445 self.database = database
448 """Initialize access attributes."""
452 def disconnect(self):
453 """Close connection with database."""
457 class SQLiteDatabaseAccess(DatabaseAccess):
459 """Child class of DatabaseAccess for SQLite access."""
461 def __init__(self, database):
462 """Use superclass method for initialisation of constructor."""
463 super(SQLiteDatabaseAccess, self).__init__(database)
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")
473 def reset_query(self):
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) + "'" + ", "
482 def add_swarm(self, swarm):
483 """Insert swarm in database. swarm type is Swarm."""
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)
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)
495 self.cursor.execute("SELECT * FROM swarms ORDER BY id DESC LIMIT 1")
496 row = self.cursor.fetchone()
499 def add_client_session(self, session):
500 """Insert session in database. session type is Session."""
502 self.append_to_insert_query("swarm_id", session.swarm_id)
503 # TODO: search database for client ID
504 self.append_to_insert_query("btclient_id",
505 bittorrent_clients[session.btclient]['id'])
506 self.append_to_insert_query("system_os", session.system_os)
507 self.append_to_insert_query("system_os_version",
508 session.system_os_version)
509 self.append_to_insert_query("system_ram", session.system_ram)
510 self.append_to_insert_query("system_cpu", session.system_cpu)
511 self.append_to_insert_query("public_ip", session.public_ip)
512 self.append_to_insert_query("public_port", session.public_port)
513 self.append_to_insert_query("ds_limit", session.ds_limit)
514 self.append_to_insert_query("us_limit", session.us_limit)
515 self.append_to_insert_query("start_time", session.start_time)
516 self.append_to_insert_query("dht_enabled", session.dht_enabled)
517 self.append_to_insert_query("pxe_enabled", session.pxe_enabled)
518 self.append_to_insert_query("streaming_enabled",
519 session.streaming_enabled)
520 self.append_to_insert_query("features", session.features)
521 self.append_to_insert_query("description", session.description)
523 self.columns = re.sub(',\s*$', '', self.columns)
524 self.values = re.sub(',\s*$', '', self.values)
525 insert_query = "INSERT INTO client_sessions(" + self.columns +")" + \
526 " VALUES(" + self.values + ")"
527 self.cursor.execute(insert_query)
529 self.cursor.execute("SELECT * FROM client_sessions "
530 "ORDER BY id DESC LIMIT 1")
531 row = self.cursor.fetchone()
534 def get_string_timestamp(self, ts):
535 # Timestamp is Python Datatime. Convert it to string format and
536 # pass it to internal SQLITE julianday() function.
537 return "%s-%s-%s %s:%s:%s.%s" \
538 %(ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second,
539 1000 * ts.microsecond)
541 def add_peer_status_message(self, msg):
542 """Insert peer status message in database.
543 msg type is PeerStatusMessage.
546 self.append_to_insert_query("client_session_id", msg.client_session_id)
548 # TODO: Check msg.timestamp is not None. Raise exception.
549 timestamp_string = self.get_string_timestamp(msg.timestamp)
550 value = "julianday(" + timestamp_string + ")"
551 self.append_to_insert_query("timestamp", value)
553 self.append_to_insert_query("timestamp", msg.timestamp)
554 self.append_to_insert_query("peer_ip", msg.peer_ip)
555 self.append_to_insert_query("peer_port", msg.peer_port)
556 self.append_to_insert_query("download_speed", msg.download_speed)
557 self.append_to_insert_query("upload_speed", msg.upload_speed)
559 self.columns = re.sub(',\s*$', '', self.columns)
560 self.values = re.sub(',\s*$', '', self.values)
561 insert_query = "INSERT INTO peer_status_messages(" + \
562 self.columns +")" + " VALUES(" + self.values + ")"
563 self.cursor.execute(insert_query)
566 def add_status_message(self, msg):
567 """Insert status msg in database. msg type is StatusMessage."""
569 self.append_to_insert_query("client_session_id", msg.client_session_id)
571 # TODO: Check msg.timestamp is not None. Raise exception.
572 timestamp_string = self.get_string_timestamp(msg.timestamp)
573 value = "julianday(" + timestamp_string + ")"
574 self.append_to_insert_query("timestamp", value)
576 self.append_to_insert_query("num_peers", msg.num_peers)
577 self.append_to_insert_query("num_dht_peers", msg.num_dht_peers)
578 self.append_to_insert_query("download_speed", msg.download_speed)
579 self.append_to_insert_query("upload_speed", msg.upload_speed)
580 self.append_to_insert_query("download_size", msg.download_size)
581 self.append_to_insert_query("upload_size", msg.upload_size)
582 self.append_to_insert_query("eta", msg.eta)
584 self.columns = re.sub(',\s*$', '', self.columns)
585 self.values = re.sub(',\s*$', '', self.values)
586 insert_query = "INSERT INTO status_messages(" + self.columns +")" + \
587 " VALUES(" + self.values + ")"
588 self.cursor.execute(insert_query)
591 def add_verbose_message(self, msg):
592 """Insert verbose msg in database. msg type is VerboseMessage."""
594 self.append_to_insert_query("client_session_id", msg.client_session_id)
596 # TODO: Check msg.timestamp is not None. Raise exception.
597 timestamp_string = self.get_string_timestamp(msg.timestamp)
598 value = "julianday(" + timestamp_string + ")"
599 self.append_to_insert_query("timestamp", value)
601 self.append_to_insert_query("transfer_direction_id",
602 transfer_directions[msg.transfer_direction])
603 self.append_to_insert_query("peer_ip", msg.peer_ip)
604 self.append_to_insert_query("peer_port", msg.peer_port)
605 self.append_to_insert_query("message_type_id",
606 message_types[msg.message_type]['id'])
607 self.append_to_insert_query("index", msg.index)
608 self.append_to_insert_query("begin", msg.begin)
609 self.append_to_insert_query("length", msg.length)
610 self.append_to_insert_query("listen_port", msg.listen_port)
612 self.columns = re.sub(',\s*$', '', self.columns)
613 self.values = re.sub(',\s*$', '', self.values)
614 insert_query = "INSERT INTO verbose_messages(" + self.columns +")" + \
615 " VALUES(" + self.values + ")"
616 self.cursor.execute(insert_query)
619 class MySQLDatabaseAccess(DatabaseAccess):
621 """Child class of DatabaseAccess for MySQL access."""
623 def __init__(self, database):
624 """Initialize the database attribute of the class.
626 Use superclass method for initialisation of constructor.
627 Check also for the optional arguments; if they are not, use defaults.
630 super(MySQLDatabaseAccess, self).__init__(database)
631 if 'host' not in self.database:
632 self.database['host'] = 'localhost'
633 if 'port' not in self.database:
634 self.database['port'] = 3306
637 """Connect to MySQL database."""
638 self.conn = MySQLdb.Connection(db=self.database['database'],
639 user=self.database['user'],
640 passwd=self.database['password'],
641 host=self.database['host'],
642 port=int(self.database['port']))
643 self.cursor = self.conn.cursor()
646 def reset_query(self):
650 def append_to_insert_query(self, data_name, data_value):
651 if data_value is not None:
652 self.columns = self.columns + data_name + ", "
653 self.values = self.values + "'" + str(data_value) + "'" + ", "
655 def add_swarm(self, swarm):
656 """Insert swarm in database. swarm type is Swarm."""
658 self.append_to_insert_query("torrent_filename", swarm.torrent_filename)
659 self.append_to_insert_query("data_size", swarm.data_size)
660 self.append_to_insert_query("description", swarm.description)
662 self.columns = re.sub(',\s*$', '', self.columns)
663 self.values = re.sub(',\s*$', '', self.values)
664 insert_query = "INSERT INTO swarms(" + self.columns +")" + \
665 " VALUES(" + self.values + ")"
666 self.cursor.execute(insert_query)
668 self.cursor.execute("SELECT * FROM swarms ORDER BY id DESC LIMIT 1")
669 row = self.cursor.fetchone()
672 def add_client_session(self, session):
673 """Insert session in database. session type is Session."""
675 self.append_to_insert_query("swarm_id", session.swarm_id)
676 # TODO: search database for client ID
677 self.append_to_insert_query("btclient_id",
678 bittorrent_clients[session.btclient]['id'])
679 self.append_to_insert_query("system_os", session.system_os)
680 self.append_to_insert_query("system_os_version",
681 session.system_os_version)
682 self.append_to_insert_query("system_ram", session.system_ram)
683 self.append_to_insert_query("system_cpu", session.system_cpu)
684 self.append_to_insert_query("public_ip", session.public_ip)
685 self.append_to_insert_query("public_port", session.public_port)
686 self.append_to_insert_query("ds_limit", session.ds_limit)
687 self.append_to_insert_query("us_limit", session.us_limit)
688 self.append_to_insert_query("start_time", session.start_time)
689 self.append_to_insert_query("dht_enabled", session.dht_enabled)
690 self.append_to_insert_query("pxe_enabled", session.pxe_enabled)
691 self.append_to_insert_query("streaming_enabled",
692 session.streaming_enabled)
693 self.append_to_insert_query("features", session.features)
694 self.append_to_insert_query("description", session.description)
696 self.columns = re.sub(',\s*$', '', self.columns)
697 self.values = re.sub(',\s*$', '', self.values)
698 insert_query = "INSERT INTO client_sessions(" + self.columns +")" + \
699 " VALUES(" + self.values + ")"
700 self.cursor.execute(insert_query)
702 self.cursor.execute("SELECT * FROM client_sessions "
703 "ORDER BY id DESC LIMIT 1")
704 row = self.cursor.fetchone()
707 def get_string_timestamp(self, ts):
708 # Timestamp is Python Datatime. Convert it to string format.
709 return "%s-%s-%s %s:%s:%s" \
710 %(ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second)
712 def add_peer_status_message(self, msg):
713 """Insert peer status message in database.
714 msg type is PeerStatusMessage.
717 self.append_to_insert_query("client_session_id", msg.client_session_id)
719 # TODO: Check msg.timestamp is not None. Raise exception.
720 timestamp_string = self.get_string_timestamp(msg.timestamp)
722 self.append_to_insert_query("timestamp", timestamp_string)
723 self.append_to_insert_query("peer_ip", msg.peer_ip)
724 self.append_to_insert_query("peer_port", msg.peer_port)
725 self.append_to_insert_query("download_speed", msg.download_speed)
726 self.append_to_insert_query("upload_speed", msg.upload_speed)
728 self.columns = re.sub(',\s*$', '', self.columns)
729 self.values = re.sub(',\s*$', '', self.values)
730 insert_query = "INSERT INTO peer_status_messages(" + \
731 self.columns +")" + " VALUES(" + self.values + ")"
732 self.cursor.execute(insert_query)
735 def add_status_message(self, msg):
736 """Insert status msg in database. msg type is StatusMessage."""
738 self.append_to_insert_query("client_session_id", msg.client_session_id)
740 # TODO: Check msg.timestamp is not None. Raise exception.
741 timestamp_string = self.get_string_timestamp(msg.timestamp)
743 self.append_to_insert_query("timestamp", timestamp_string)
744 self.append_to_insert_query("num_peers", msg.num_peers)
745 self.append_to_insert_query("num_dht_peers", msg.num_dht_peers)
746 self.append_to_insert_query("download_speed", msg.download_speed)
747 self.append_to_insert_query("upload_speed", msg.upload_speed)
748 self.append_to_insert_query("download_size", msg.download_size)
749 self.append_to_insert_query("upload_size", msg.upload_size)
750 self.append_to_insert_query("eta", msg.eta)
752 self.columns = re.sub(',\s*$', '', self.columns)
753 self.values = re.sub(',\s*$', '', self.values)
754 insert_query = "INSERT INTO status_messages(" + self.columns +")" + \
755 " VALUES(" + self.values + ")"
756 self.cursor.execute(insert_query)
759 def add_verbose_message(self, msg):
760 """Insert verbose msg in database. msg type is VerboseMessage."""
762 self.append_to_insert_query("client_session_id", msg.client_session_id)
764 # TODO: Check msg.timestamp is not None. Raise exception.
765 timestamp_string = self.get_string_timestamp(msg.timestamp)
767 self.append_to_insert_query("timestamp", timestamp_string)
768 self.append_to_insert_query("transfer_direction_id",
769 transfer_directions[msg.transfer_direction])
770 self.append_to_insert_query("peer_ip", msg.peer_ip)
771 self.append_to_insert_query("peer_port", msg.peer_port)
772 self.append_to_insert_query("message_type_id",
773 message_types[msg.message_type]['id'])
774 self.append_to_insert_query("index", msg.index)
775 self.append_to_insert_query("begin", msg.begin)
776 self.append_to_insert_query("length", msg.length)
777 self.append_to_insert_query("listen_port", msg.listen_port)
779 self.columns = re.sub(',\s*$', '', self.columns)
780 self.values = re.sub(',\s*$', '', self.values)
781 insert_query = "INSERT INTO verbose_messages(" + self.columns +")" + \
782 " VALUES(" + self.values + ")"
783 self.cursor.execute(insert_query)
786 class SwarmWriter(object):
787 """Wrapper class for swarm storage write actions.
789 Multiple *Access objects may be added to the clas resulting in multiple
791 For example, adding a swarm could result in
792 * adding a table entry in a MySQL database (MySQLDatabaseAccess)
793 * adding a table entry in an SQLite database (SQLiteDatabaseAccess)
794 * adding a new folder in a tree structure (TreeTextFileAccess)
800 def add_access_handle(self, handle):
801 self.handlers.append(handle)
803 def remove_access_handle(self, handle):
804 self.handlers.remove(handle)
806 def add_swarm(self, swarm):
807 for h in self.handlers:
808 swarm_id = h.add_swarm(swarm)
811 def add_client_session(self, session):
812 for h in self.handlers:
813 session_id = h.add_client_session(session)
816 def add_peer_status_message(self, msg):
817 for h in self.handlers:
818 h.add_peer_status_message(msg)
820 def add_status_message(self, msg):
821 for h in self.handlers:
822 h.add_status_message(msg)
824 def add_verbose_message(self, msg):
825 for h in self.handlers:
826 h.add_verbose_message(msg)