From 004b68bdd8a10f3bb716fb137ecf94f5a521eb48 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Mon, 29 Oct 2012 12:33:47 +0100 Subject: [PATCH] database is now a class, and the file is in ~/.config. --- file_sharing/database.py | 345 ++++++++++++++++--------------- file_sharing/fileshare_window.py | 39 ++-- file_sharing/fshare.py | 19 +- file_sharing/fshare_protocol.py | 14 +- 4 files changed, 211 insertions(+), 206 deletions(-) diff --git a/file_sharing/database.py b/file_sharing/database.py index f9a3146..8bb4bbe 100644 --- a/file_sharing/database.py +++ b/file_sharing/database.py @@ -2,192 +2,197 @@ import sqlite3 from common import gajim import sys import os -path = sys.path[1] -path = path + '/file_sharing/' + 'shared_files.db' -db_exist = os.path.exists(path) -conn = sqlite3.connect(path) -# Enable foreign keys contraints -conn.cursor().execute("pragma foreign_keys = on") -# NOTE: Make sure we are getting and setting the requester without its resource -def create_database(): - c = conn.cursor() - # Create tables - c.execute("CREATE TABLE permissions" + - "(fid integer REFERENCES files(fid) ON DELETE CASCADE, " + - "account text, requester text)") - c.execute("CREATE TABLE files" + - "(fid INTEGER PRIMARY KEY AUTOINCREMENT," + - " file_path text, relative_path text, hash_sha1 text," + - "size numeric, description text, mod_date text, is_dir boolean)") - # Save (commit) the changes - conn.commit() - c.close() +class FilesharingDatabase: + def __init__(self, plugin): + self.plugin = plugin + path_l = os.path.split(plugin.config.FILE_PATH) + path = os.path.join(path_l[0], 'shared_files.db') + db_exist = os.path.exists(path) + print path + self.conn = sqlite3.connect(path) + # Enable foreign keys contraints + self.conn.cursor().execute("pragma foreign_keys = on") + if not db_exist: + self.create_database() -def get_toplevel_files(account, requester): - c = conn.cursor() - data = (account, requester) - c.execute("SELECT relative_path, hash_sha1, size, description, mod_date," + - " is_dir FROM (files JOIN permissions ON" + - " files.fid=permissions.fid) WHERE account=? AND requester=?" + - " AND relative_path NOT LIKE '%/%'", data) - result = c.fetchall() - c.close() - return result + # NOTE: Make sure we are getting and setting the requester without its resource + def create_database(self): + c = self.conn.cursor() + # Create tables + c.execute("CREATE TABLE permissions" + + "(fid integer REFERENCES files(fid) ON DELETE CASCADE, " + + "account text, requester text)") + c.execute("CREATE TABLE files" + + "(fid INTEGER PRIMARY KEY AUTOINCREMENT," + + " file_path text, relative_path text, hash_sha1 text," + + "size numeric, description text, mod_date text, is_dir boolean)") + # Save (commit) the changes + self.conn.commit() + c.close() -def get_files_from_dir(account, requester, dir_): - c = conn.cursor() - data = (account, requester, dir_ + '/%') - c.execute("SELECT relative_path, hash_sha1, size, description, mod_date," + - " is_dir FROM (files JOIN permissions ON" + - " files.fid=permissions.fid) WHERE account=? AND requester=?" + - " AND relative_path LIKE ?", data) - result = c.fetchall() - c.close() - fresult = [] - for r in result: - name = r[0][len(dir_) + 1:] - if '/' not in name: - fresult.append(r) - return fresult - -def get_files(account, requester): - """ - >>> file_ = ('file_path', 'relative_path', 'hash', 999, 'description', \ - 'date', False) - >>> foo = add_file('account@gajim', 'requester@jabber', file_) - >>> result = get_files('account@gajim', 'requester@jabber') - >>> len(result) - 1 - >>> _delete_file(1) - """ - c = conn.cursor() - data = (account, requester) - c.execute("SELECT relative_path, hash_sha1, size, description, mod_date," + - " is_dir FROM (files JOIN permissions ON" + - " files.fid=permissions.fid) WHERE account=? AND requester=?", data) - result = c.fetchall() - c.close() - return result - -def get_file(account, requester, hash_, name): - c = conn.cursor() - if hash_: - data = (account, requester, hash_) - sql = "SELECT relative_path, hash_sha1, size, description, mod_date," + \ - " file_path FROM (files JOIN permissions ON" + \ - " files.fid=permissions.fid) WHERE account=? AND requester=?" + \ - " AND hash_sha1=?" - else: - data = (account, requester, name) - sql = "SELECT relative_path, hash_sha1, size, description, mod_date," + \ - " file_path FROM (files JOIN permissions ON" + \ - " files.fid=permissions.fid) WHERE account=? AND requester=?" + \ - " AND relative_path=?" - c.execute(sql, data) - result = c.fetchall() - c.close() - if result == []: - return None - else: - return result[0] - -def get_files_name(account, requester): - result = get_files(account, requester) - flist = [] - for r in result: - flist.append(r[0]) - return flist - -def add_file(account, requester, file_): - """ - >>> file_ = ('file_path', 'relative_path', 'hash', 999, 'description', \ - 'date', False) - >>> add_file('account@gajim', 'requester@jabber', file_) - 1 - >>> _delete_file(1) - """ - _check_duplicate(account, requester, file_) - requester = gajim.get_jid_without_resource(requester) - c = conn.cursor() - c.execute("INSERT INTO files (file_path, " + - "relative_path, hash_sha1, size, description, mod_date, " + - " is_dir) VALUES (?,?,?,?,?,?,?)", - file_) - fid = c.lastrowid - permission_data = (fid, account, requester) - c.execute("INSERT INTO permissions VALUES (?,?,?)", permission_data) - conn.commit() - c.close() - return fid - -def _check_duplicate(account, requester, file_): - c = conn.cursor() - data = (account, requester, file_[1]) - c.execute("SELECT * FROM (files JOIN permissions ON" + - " files.fid=permissions.fid) WHERE account=? AND requester=?" + - " AND relative_path=? ", data) - result = c.fetchall() - if file_[2] != '': - data = (account, requester, file_[2]) - c.execute("SELECT * FROM (files JOIN permissions ON" + + def get_toplevel_files(self, account, requester): + c = self.conn.cursor() + data = (account, requester) + c.execute("SELECT relative_path, hash_sha1, size, description, mod_date," + + " is_dir FROM (files JOIN permissions ON" + " files.fid=permissions.fid) WHERE account=? AND requester=?" + - " AND hash_sha1=?)", data) - result.extend(c.fetchall()) - if len(result) > 0: - raise Exception('Duplicated entry') - c.close() + " AND relative_path NOT LIKE '%/%'", data) + result = c.fetchall() + c.close() + return result -def _delete_file(fid): - c = conn.cursor() - data = (fid, ) - c.execute("DELETE FROM files WHERE fid=?", data) - conn.commit() - c.close() + def get_files_from_dir(self, account, requester, dir_): + c = self.conn.cursor() + data = (account, requester, dir_ + '/%') + c.execute("SELECT relative_path, hash_sha1, size, description, mod_date," + + " is_dir FROM (files JOIN permissions ON" + + " files.fid=permissions.fid) WHERE account=? AND requester=?" + + " AND relative_path LIKE ?", data) + result = c.fetchall() + c.close() + fresult = [] + for r in result: + name = r[0][len(dir_) + 1:] + if '/' not in name: + fresult.append(r) + return fresult -def _delete_dir(dir_, account, requester): - c = conn.cursor() - data = (account, requester, dir_, dir_ + '/%') - sql = "DELETE FROM files WHERE fid IN " + \ - " (SELECT files.fid FROM files, permissions WHERE" + \ - " files.fid=permissions.fid AND account=?"+ \ - " AND requester=? AND (relative_path=? OR relative_path LIKE ?))" - c.execute(sql, data) - conn.commit() - c.close() + def get_files(self, account, requester): + """ + >>> file_ = ('file_path', 'relative_path', 'hash', 999, 'description', \ + 'date', False) + >>> foo = add_file('account@gajim', 'requester@jabber', file_) + >>> result = get_files('account@gajim', 'requester@jabber') + >>> len(result) + 1 + >>> _delete_file(1) + """ + c = self.conn.cursor() + data = (account, requester) + c.execute("SELECT relative_path, hash_sha1, size, description, mod_date," + + " is_dir FROM (files JOIN permissions ON" + + " files.fid=permissions.fid) WHERE account=? AND requester=?", data) + result = c.fetchall() + c.close() + return result -def delete(account, requester, relative_path): - c = conn.cursor() - data = (account, requester, relative_path) - c.execute("SELECT files.fid, is_dir FROM (files JOIN permissions ON" + - " files.fid=permissions.fid) WHERE account=? AND requester=? AND " + - "relative_path=? ", data) - result = c.fetchone() - c.close() - if result[1] == 0: - _delete_file(result[0]) - else: - _delete_dir(relative_path, account, requester) + def get_file(self, account, requester, hash_, name): + c = self.conn.cursor() + if hash_: + data = (account, requester, hash_) + sql = "SELECT relative_path, hash_sha1, size, description, mod_date," + \ + " file_path FROM (files JOIN permissions ON" + \ + " files.fid=permissions.fid) WHERE account=? AND requester=?" + \ + " AND hash_sha1=?" + else: + data = (account, requester, name) + sql = "SELECT relative_path, hash_sha1, size, description, mod_date," + \ + " file_path FROM (files JOIN permissions ON" + \ + " files.fid=permissions.fid) WHERE account=? AND requester=?" + \ + " AND relative_path=?" + c.execute(sql, data) + result = c.fetchall() + c.close() + if result == []: + return None + else: + return result[0] -def delete_all(account, requester): - c = conn.cursor() - data = (account, requester) - sql = "DELETE FROM files WHERE fid IN (SELECT fid FROM permissions" + \ - " WHERE account=? AND requester=?)" - c.execute(sql, data) - conn.commit() - c.close() + def get_files_name(self, account, requester): + result = self.get_files(account, requester) + flist = [] + for r in result: + flist.append(r[0]) + return flist + + def add_file(self, account, requester, file_): + """ + >>> file_ = ('file_path', 'relative_path', 'hash', 999, 'description', \ + 'date', False) + >>> add_file('account@gajim', 'requester@jabber', file_) + 1 + >>> _delete_file(1) + """ + self._check_duplicate(account, requester, file_) + requester = gajim.get_jid_without_resource(requester) + c = self.conn.cursor() + c.execute("INSERT INTO files (file_path, " + + "relative_path, hash_sha1, size, description, mod_date, " + + " is_dir) VALUES (?,?,?,?,?,?,?)", + file_) + fid = c.lastrowid + permission_data = (fid, account, requester) + c.execute("INSERT INTO permissions VALUES (?,?,?)", permission_data) + self.conn.commit() + c.close() + return fid + + def _check_duplicate(self, account, requester, file_): + c = self.conn.cursor() + data = (account, requester, file_[1]) + c.execute("SELECT * FROM (files JOIN permissions ON" + + " files.fid=permissions.fid) WHERE account=? AND requester=?" + + " AND relative_path=? ", data) + result = c.fetchall() + if file_[2] != '': + data = (account, requester, file_[2]) + c.execute("SELECT * FROM (files JOIN permissions ON" + + " files.fid=permissions.fid) WHERE account=? AND requester=?" + + " AND hash_sha1=?)", data) + result.extend(c.fetchall()) + if len(result) > 0: + raise Exception('Duplicated entry') + c.close() + + def _delete_file(self, fid): + c = self.conn.cursor() + data = (fid, ) + c.execute("DELETE FROM files WHERE fid=?", data) + self.conn.commit() + c.close() + + def _delete_dir(self, dir_, account, requester): + c = self.conn.cursor() + data = (account, requester, dir_, dir_ + '/%') + sql = "DELETE FROM files WHERE fid IN " + \ + " (SELECT files.fid FROM files, permissions WHERE" + \ + " files.fid=permissions.fid AND account=?"+ \ + " AND requester=? AND (relative_path=? OR relative_path LIKE ?))" + c.execute(sql, data) + self.conn.commit() + c.close() + + def delete(self, account, requester, relative_path): + c = self.conn.cursor() + data = (account, requester, relative_path) + c.execute("SELECT files.fid, is_dir FROM (files JOIN permissions ON" + + " files.fid=permissions.fid) WHERE account=? AND requester=? AND " + + "relative_path=? ", data) + result = c.fetchone() + c.close() + if result[1] == 0: + self._delete_file(result[0]) + else: + self._delete_dir(relative_path, account, requester) + + def delete_all(self, account, requester): + c = self.conn.cursor() + data = (account, requester) + sql = "DELETE FROM files WHERE fid IN (SELECT fid FROM permissions" + \ + " WHERE account=? AND requester=?)" + c.execute(sql, data) + self.conn.commit() + c.close() -if not db_exist: - create_database() if __name__ == "__main__": """ DELETE DATABASE FILE BEFORE RUNNING TESTS """ import doctest path = sys.path[0] - path = path + '/' + 'shared_files.db' + path = path + '/' + 'shared_files.db' conn = sqlite3.connect(path) # Enable foreign keys contraints conn.cursor().execute("pragma foreign_keys = on") diff --git a/file_sharing/fileshare_window.py b/file_sharing/fileshare_window.py index 3699ade..1aa9fd2 100644 --- a/file_sharing/fileshare_window.py +++ b/file_sharing/fileshare_window.py @@ -1,4 +1,3 @@ -import database import gtk import gobject from common import gajim @@ -65,9 +64,9 @@ class FileShareWindow(gtk.Window): self.empty_row_child = {} # Manage page self.bt_add_file = gtk.Button('Add file') - self.bt_add_file.connect('clicked', self.add_file) + self.bt_add_file.connect('clicked', self.add_file) self.bt_add_dir = gtk.Button('Add directory') - self.bt_add_dir.connect('clicked', self.add_directory) + self.bt_add_dir.connect('clicked', self.add_directory) self.bt_remove = gtk.Button('Remove') self.bt_remove.connect('clicked', self.remove_file_clicked) self.bt_remove_all = gtk.Button('Remove all') @@ -117,7 +116,7 @@ class FileShareWindow(gtk.Window): self.entry_dir_pref = gtk.Entry(max=0) self.entry_dir_pref.set_text(self.plugin.config['incoming_dir']) self.bt_sel_dir_pref = gtk.Button('Select dir', gtk.STOCK_OPEN) - self.bt_sel_dir_pref.connect('clicked', self.on_bt_sel_dir_pref_clicked) + self.bt_sel_dir_pref.connect('clicked', self.on_bt_sel_dir_pref_clicked) self.frm_dir_pref = gtk.Frame('Incoming files directory') self.frm_dir_pref.set_shadow_type(gtk.SHADOW_IN) self.pref_hbox = gtk.HBox() @@ -137,7 +136,7 @@ class FileShareWindow(gtk.Window): pro.set_window(self) def add_file(self, widget): - dialog = gtk.FileChooserDialog('Add file to be shared', self, + dialog = gtk.FileChooserDialog('Add file to be shared', self, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK) @@ -150,7 +149,7 @@ class FileShareWindow(gtk.Window): dialog.destroy() def add_directory(self, widget): - dialog = gtk.FileChooserDialog('Add directory to be shared', self, + dialog = gtk.FileChooserDialog('Add directory to be shared', self, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT) @@ -172,15 +171,15 @@ class FileShareWindow(gtk.Window): dirpath = f.split('/') if len(dirpath) == 1: # Top level file, it doesnt have parent, add it right away - fref[dirpath[0]] = treestore.insert(root, - 0, + fref[dirpath[0]] = treestore.insert(root, + 0, (dirpath[0],) ) else: for dir_ in dirpath: if tail + dir_ not in fref: fref[tail + dir_] = treestore.append( - parent, + parent, (dir_,) ) parent = fref[tail + dir_] @@ -191,7 +190,7 @@ class FileShareWindow(gtk.Window): # Converts date-time from seconds from epoch to iso 8601 import time, datetime ts = time.gmtime(epoch) - dt = datetime.datetime(ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, + dt = datetime.datetime(ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec) return dt.isoformat() @@ -215,11 +214,11 @@ class FileShareWindow(gtk.Window): is_dir = os.path.isdir(f) mod_date = os.path.getmtime(f) mod_date = self.__convert_date(mod_date) - # TODO: add hash + # TODO: add hash file_ = (f, relative_name, '', size, '', mod_date, is_dir) requester = self.cbb_contacts.get_active_text() try: - fid = database.add_file(self.account, requester, file_) + fid = self.plugin.database.add_file(self.account, requester, file_) except Exception, e: if e == 'Duplicated entry': print 'Error: ' + e @@ -242,7 +241,7 @@ class FileShareWindow(gtk.Window): con.supports(fshare_protocol.NS_FILE_SHARING): break cjid = con.get_full_jid() - r = self.ts_search.insert(None, 0, + r = self.ts_search.insert(None, 0, (cjid, )) self.browse_jid[cjid] = r pro = fshare.FileSharePlugin.prohandler[self.account] @@ -256,14 +255,14 @@ class FileShareWindow(gtk.Window): self.cbb_contacts.grab_focus() for c in gajim.contacts.iter_contacts(self.account): jid = gajim.get_jid_without_resource(c.get_full_jid()) - r = self.ts_contacts.insert(None, len(self.ts_contacts), + r = self.ts_contacts.insert(None, len(self.ts_contacts), (jid, )) if c.get_full_jid() == contact.get_full_jid(): self.cbb_contacts.set_active_iter(r) self.contacts_rows.append(r) self.manage_vbox2.set_sensitive(True) self.bt_remove.set_sensitive(False) - self.add_file_list(database.get_files_name(self.account, + self.add_file_list(self.plugin.database.get_files_name(self.account, gajim.get_jid_without_resource(contact.get_full_jid())), self.ts_files ) @@ -281,7 +280,7 @@ class FileShareWindow(gtk.Window): # If the contact in the comboboxentry is include inside of the # combobox if contact == self.ts_contacts.get_value(i, 0): - self.add_file_list(database.get_files_name(self.account, + self.add_file_list(self.plugin.database.get_files_name(self.account, contact), self.ts_files ) @@ -295,13 +294,13 @@ class FileShareWindow(gtk.Window): sel = self.treeSelection_files.get_selected() relative_name = self.ts_files.get_value(sel[1], 0) self.ts_files.remove(sel[1]) - database.delete(self.account, contact, relative_name) + self.plugin.database.delete(self.account, contact, relative_name) widget.set_sensitive(False) def remove_all_clicked(self, widget, data=None): entry = self.cbb_contacts.child contact = entry.get_text() - database.delete_all(self.account, contact) + self.plugin.database.delete_all(self.account, contact) self.ts_files.clear() def row_selected(self, widget, data=None): @@ -332,7 +331,7 @@ class FileShareWindow(gtk.Window): pro = fshare.FileSharePlugin.prohandler[self.account] contact = self.get_contact_from_iter(self.ts_search, i) contact = gajim.contacts.get_contact_with_highest_priority( - self.account, + self.account, contact ) stanza = pro.request(contact.get_full_jid(), name, isFile=False) if pro.conn.connection: @@ -393,7 +392,7 @@ class FileShareWindow(gtk.Window): def on_bt_sel_dir_pref_clicked(self, widget, data=None): chooser = gtk.FileChooserDialog(title='Incoming files directory', - action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, + action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,gtk.RESPONSE_OK)) response = chooser.run() diff --git a/file_sharing/fshare.py b/file_sharing/fshare.py index 1bbe5bd..9584ee9 100644 --- a/file_sharing/fshare.py +++ b/file_sharing/fshare.py @@ -27,14 +27,15 @@ class FileSharePlugin(GajimPlugin): self.activated = False self.description = _('This plugin allows you to share folders'+ ' with a peer using jingle file transfer.') - self.config_dialog = None + self.config_dialog = None home_path = os.path.expanduser('~/') self.config_default_values = {'incoming_dir': (home_path, '')} + self.database = database.FilesharingDatabase(self) # Create one protocol handler per account accounts = gajim.contacts.get_accounts() for account in gajim.contacts.get_accounts(): FileSharePlugin.prohandler[account] = \ - fshare_protocol.protocol(account) + fshare_protocol.Protocol(account, self) self.events_handlers = { 'raw-iq-received': (ged.CORE, self._nec_raw_iq) } @@ -46,7 +47,7 @@ class FileSharePlugin(GajimPlugin): gajim.gajim_common_features.append(fshare_protocol.NS_FILE_SHARING) self._compute_caps_hash() # Replace the contact menu - self.__get_contact_menu = gui_menu_builder.get_contact_menu + self.__get_contact_menu = gui_menu_builder.get_contact_menu gui_menu_builder.get_contact_menu = self.contact_menu # Replace get_file_info for account in gajim.contacts.get_accounts(): @@ -99,10 +100,10 @@ class FileSharePlugin(GajimPlugin): msf = gtk.MenuItem('Manage shared files') msf.connect('activate', self.manage_menu_clicked, account, contact) enable_fs = gtk.CheckMenuItem('Enable file sharing') - enable_fs.set_active(True) - submenu.attach(bf, 0, 1, 0, 1) - submenu.attach(msf, 0, 1, 1, 2) - submenu.attach(enable_fs, 0, 1, 2, 3) + enable_fs.set_active(True) + submenu.attach(bf, 0, 1, 0, 1) + submenu.attach(msf, 0, 1, 1, 2) + submenu.attach(enable_fs, 0, 1, 2, 3) if gajim.account_is_disconnected(account) or \ contact.show in ('offline', 'error') or not \ contact.supports(fshare_protocol.NS_FILE_SHARING): @@ -122,13 +123,13 @@ class FileSharePlugin(GajimPlugin): file_info = self._get_file_info(hash_, name) if file_info: return file_info - raw_info = database.get_file(account, peerjid, hash_, name) + raw_info = self.database.get_file(account, peerjid, hash_, name) file_info = {'name': raw_info[0], 'file-name' : raw_info[5], 'hash' : raw_info[1], 'size' : raw_info[2], 'date' : raw_info[4], - 'peerjid' : peerjid + 'peerjid' : peerjid } return file_info diff --git a/file_sharing/fshare_protocol.py b/file_sharing/fshare_protocol.py index b120c00..3247b46 100644 --- a/file_sharing/fshare_protocol.py +++ b/file_sharing/fshare_protocol.py @@ -3,14 +3,14 @@ from common import helpers from common import gajim from common import XMPPDispatcher from common.xmpp import Hashes -import database # Namespace for file sharing NS_FILE_SHARING = 'http://gajim.org/protocol/filesharing' -class protocol(): +class Protocol(): - def __init__(self, account): + def __init__(self, account, plugin): self.account = account + self.plugin = plugin self.conn = gajim.connections[self.account] # get our jid with resource self.ourjid = gajim.get_jid_from_account(self.account) @@ -52,12 +52,12 @@ class protocol(): if req.getTag('directory') and not \ req.getTag('directory').getChildren(): # We just received a toplevel directory request - files = database.get_toplevel_files(self.account, jid) + files = self.plugin.database.get_toplevel_files(self.account, jid) response = self.offer(stanza.getID(), fjid, files) self.conn.connection.send(response) elif req.getTag('directory') and req.getTag('directory').getTag('name'): dir_ = req.getTag('directory').getTag('name').getData()[1:] - files = database.get_files_from_dir(self.account, jid, dir_) + files = self.plugin.database.get_files_from_dir(self.account, jid, dir_) response = self.offer(stanza.getID(), fjid, files) self.conn.connection.send(response) @@ -73,7 +73,7 @@ class protocol(): for f in info[0]: flist.append(f['name']) flist.extend(info[1]) - self.fsw.browse_fref = self.fsw.add_file_list(flist, self.fsw.ts_search, + self.fsw.browse_fref = self.fsw.add_file_list(flist, self.fsw.ts_search, self.fsw.browse_fref, self.fsw.browse_jid[fjid] ) @@ -109,7 +109,7 @@ class protocol(): pass def offer(self, id_, contact, items): - iq = xmpp.Iq(typ='result', to=contact, frm=self.ourjid, + iq = xmpp.Iq(typ='result', to=contact, frm=self.ourjid, attrs={'id': id_}) match = iq.addChild(name='match', namespace=NS_FILE_SHARING) offer = match.addChild(name='offer')