initial commit for file_share plugin
This commit is contained in:
1
file_sharing/__init__.py
Normal file
1
file_sharing/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from fshare import FileSharePlugin
|
||||
26
file_sharing/config.py
Normal file
26
file_sharing/config.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import ConfigParser
|
||||
import sys
|
||||
import os
|
||||
|
||||
def set(option, value):
|
||||
_file = open(path, 'w')
|
||||
cp.set('General', option, value)
|
||||
cp.write(_file)
|
||||
_file.close()
|
||||
|
||||
def set_defaults():
|
||||
cp.add_section('General')
|
||||
set('incoming_dir', '/home')
|
||||
|
||||
path = sys.path[1]
|
||||
path = path + '/file_sharing/' + 'conf.cfg'
|
||||
cp = ConfigParser.ConfigParser()
|
||||
if os.path.exists(path):
|
||||
_file = open(path)
|
||||
cp.readfp(_file)
|
||||
_file.close()
|
||||
else:
|
||||
set_defaults()
|
||||
INCOMING_DIR = cp.get('General', 'incoming_dir')
|
||||
|
||||
|
||||
195
file_sharing/database.py
Normal file
195
file_sharing/database.py
Normal file
@@ -0,0 +1,195 @@
|
||||
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()
|
||||
|
||||
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
|
||||
|
||||
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" +
|
||||
" 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(fid):
|
||||
c = conn.cursor()
|
||||
data = (fid, )
|
||||
c.execute("DELETE FROM files WHERE fid=?", data)
|
||||
conn.commit()
|
||||
c.close()
|
||||
|
||||
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 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 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()
|
||||
|
||||
|
||||
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'
|
||||
conn = sqlite3.connect(path)
|
||||
# Enable foreign keys contraints
|
||||
conn.cursor().execute("pragma foreign_keys = on")
|
||||
create_database()
|
||||
doctest.testmod()
|
||||
411
file_sharing/fileshare_window.py
Normal file
411
file_sharing/fileshare_window.py
Normal file
@@ -0,0 +1,411 @@
|
||||
import database
|
||||
import gtk
|
||||
import gobject
|
||||
from common import gajim
|
||||
from common import helpers
|
||||
from common.file_props import FilesProp
|
||||
import fshare
|
||||
import os
|
||||
import fshare_protocol
|
||||
import config
|
||||
|
||||
class FileShareWindow(gtk.Window):
|
||||
|
||||
def __init__(self):
|
||||
gtk.Window.__init__(self)
|
||||
self.set_title('File Share')
|
||||
self.connect('delete_event', self.delete_event)
|
||||
self.set_position(gtk.WIN_POS_CENTER)
|
||||
self.set_default_size(400, 400)
|
||||
# Children
|
||||
self.notebook = gtk.Notebook()
|
||||
# Browse page
|
||||
self.bt_search = gtk.Button('Search')
|
||||
self.bt_search.connect('clicked', self.on_bt_search_clicked)
|
||||
self.entry_search = gtk.Entry(max=0)
|
||||
self.entry_search.set_size_request(300, -1)
|
||||
self.exp_advance = gtk.Expander('Advance')
|
||||
self.ts_search = gtk.TreeStore(gobject.TYPE_STRING)
|
||||
self.tv_search = gtk.TreeView(self.ts_search)
|
||||
self.tv_search.connect('row-expanded', self.row_expanded)
|
||||
self.tv_search.connect('button-press-event',
|
||||
self.on_treeview_button_press_event)
|
||||
self.browse_popup = gtk.Menu()
|
||||
mi_download = gtk.MenuItem('Download')
|
||||
mi_property = gtk.MenuItem('Property')
|
||||
mi_download.show()
|
||||
mi_property.show()
|
||||
mi_download.connect('activate', self.on_download_clicked)
|
||||
self.browse_popup.append(mi_download)
|
||||
self.browse_popup.append(mi_property)
|
||||
self.browse_sw = gtk.ScrolledWindow()
|
||||
self.browse_sw.add(self.tv_search)
|
||||
self.tvcolumn_browse = gtk.TreeViewColumn('')
|
||||
self.tv_search.append_column(self.tvcolumn_browse)
|
||||
self.cell_browse = gtk.CellRendererText()
|
||||
self.tvcolumn_browse.pack_start(self.cell_browse, True)
|
||||
self.tvcolumn_browse.add_attribute(self.cell_browse, 'text', 0)
|
||||
self.lbl_browse = gtk.Label('Browse')
|
||||
self.browse_hbox = gtk.HBox()
|
||||
self.browse_hbox2 = gtk.HBox()
|
||||
self.browse_vbox = gtk.VBox()
|
||||
self.browse_hbox.pack_start(self.entry_search, True, True, 10)
|
||||
self.browse_hbox.pack_start(self.bt_search, False, False, 10)
|
||||
self.browse_vbox.pack_start(self.browse_hbox, False, False, 10)
|
||||
self.browse_vbox.pack_start(self.exp_advance, False, False, 10)
|
||||
self.browse_vbox.pack_start(self.browse_sw, True, True, 10)
|
||||
self.notebook.append_page(self.browse_vbox, self.lbl_browse)
|
||||
# file references for tv_search
|
||||
self.browse_fref = {}
|
||||
# contact references for tv_search
|
||||
self.browse_jid = {}
|
||||
# Information of the files inserted in the treeview: name, size, etc
|
||||
self.brw_file_info = {}
|
||||
# dummy row children so that we can get expanders
|
||||
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_dir = gtk.Button('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')
|
||||
self.bt_remove_all.connect('clicked', self.remove_all_clicked)
|
||||
self.ts_contacts = gtk.TreeStore(gobject.TYPE_STRING)
|
||||
self.cbb_contacts = gtk.ComboBoxEntry(self.ts_contacts)
|
||||
self.cbb_contacts.connect('changed', self.__check_combo_edit)
|
||||
cbb_entry = self.cbb_contacts.child
|
||||
self.cbb_completion = gtk.EntryCompletion()
|
||||
cbb_entry.set_completion(self.cbb_completion)
|
||||
self.cbb_completion.set_model(self.ts_contacts)
|
||||
self.cbb_completion.set_text_column(0)
|
||||
self.bt_stophash = gtk.Button('Calculating hash')
|
||||
self.lbl_manage = gtk.Label('Manage Shared Files')
|
||||
self.ts_files = gtk.TreeStore(gobject.TYPE_STRING)
|
||||
self.tv_files = gtk.TreeView(self.ts_files)
|
||||
self.treeSelection_files = self.tv_files.get_selection()
|
||||
self.treeSelection_files.connect('changed', self.row_selected)
|
||||
self.manage_sw = gtk.ScrolledWindow()
|
||||
self.manage_sw.add(self.tv_files)
|
||||
self.tvcolumn = gtk.TreeViewColumn('')
|
||||
self.tv_files.append_column(self.tvcolumn)
|
||||
self.cell = gtk.CellRendererText()
|
||||
self.tvcolumn.pack_start(self.cell, True)
|
||||
self.tvcolumn.add_attribute(self.cell, 'text', 0)
|
||||
self.pb_filehash = gtk.ProgressBar()
|
||||
self.manage_hbox = gtk.HBox()
|
||||
self.manage_hbox2 = gtk.HBox()
|
||||
self.manage_vbox = gtk.VBox()
|
||||
self.manage_vbox2 = gtk.VBox()
|
||||
self.manage_hbox.pack_start(self.bt_stophash , False, False, 10)
|
||||
self.manage_hbox.pack_start(self.pb_filehash, True, True, 10)
|
||||
self.manage_hbox.set_sensitive(False)
|
||||
self.manage_vbox.pack_start(self.cbb_contacts, False, False, 10)
|
||||
self.manage_vbox.pack_start(self.manage_hbox, False, False, 10)
|
||||
self.manage_hbox2.pack_start(self.manage_sw, True, True, 10)
|
||||
self.manage_hbox2.pack_start(self.manage_vbox2, False, False, 10)
|
||||
self.manage_vbox2.pack_start(self.bt_add_file, False, False, 10)
|
||||
self.manage_vbox2.pack_start(self.bt_add_dir, False, False, 10)
|
||||
self.manage_vbox2.pack_start(self.bt_remove, False, False, 10)
|
||||
self.manage_vbox2.pack_start(self.bt_remove_all, False, False, 10)
|
||||
self.manage_vbox2.set_sensitive(False)
|
||||
self.manage_vbox.pack_start(self.manage_hbox2, True, True, 10)
|
||||
self.notebook.append_page(self.manage_vbox, self.lbl_manage)
|
||||
# Preferences page
|
||||
self.lbl_pref = gtk.Label('Preferences')
|
||||
self.entry_dir_pref = gtk.Entry(max=0)
|
||||
self.entry_dir_pref.set_text(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.frm_dir_pref = gtk.Frame('Incoming files directory')
|
||||
self.frm_dir_pref.set_shadow_type(gtk.SHADOW_IN)
|
||||
self.pref_hbox = gtk.HBox()
|
||||
self.pref_vbox = gtk.VBox()
|
||||
self.pref_hbox.pack_start(self.entry_dir_pref, True, True, 10)
|
||||
self.pref_hbox.pack_start(self.bt_sel_dir_pref, False, False, 10)
|
||||
self.frm_dir_pref.add(self.pref_hbox)
|
||||
self.pref_vbox.pack_start(self.frm_dir_pref, False, False, 10)
|
||||
self.notebook.append_page(self.pref_vbox , self.lbl_pref)
|
||||
self.add(self.notebook)
|
||||
self.show_all()
|
||||
|
||||
def set_account(self, account):
|
||||
self.account = account
|
||||
# connect window to protocol
|
||||
pro = fshare.FileSharePlugin.prohandler[self.account]
|
||||
pro.set_window(self)
|
||||
|
||||
def add_file(self, widget):
|
||||
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)
|
||||
)
|
||||
dialog.set_select_multiple(True)
|
||||
response = dialog.run()
|
||||
if response == gtk.RESPONSE_OK:
|
||||
file_list = dialog.get_filenames()
|
||||
self.add_items_tvcontacts(file_list)
|
||||
dialog.destroy()
|
||||
|
||||
def add_directory(self, widget):
|
||||
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)
|
||||
)
|
||||
response = dialog.run()
|
||||
if response == gtk.RESPONSE_ACCEPT:
|
||||
file_list = []
|
||||
file_list.append(dialog.get_filename())
|
||||
self.add_items_tvcontacts(file_list)
|
||||
dialog.destroy()
|
||||
|
||||
def add_file_list(self, flist, treestore, fref = {}, root = None):
|
||||
# file references to their in the treeStore
|
||||
for f in flist:
|
||||
# keeps track of the relative dir path
|
||||
tail = ''
|
||||
# keeps track of the parent row
|
||||
parent = None
|
||||
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,
|
||||
(dirpath[0],)
|
||||
)
|
||||
else:
|
||||
for dir_ in dirpath:
|
||||
if tail + dir_ not in fref:
|
||||
fref[tail + dir_] = treestore.append(
|
||||
parent,
|
||||
(dir_,)
|
||||
)
|
||||
parent = fref[tail + dir_]
|
||||
tail = tail + dir_ + '/'
|
||||
return fref
|
||||
|
||||
def __convert_date(self, epoch):
|
||||
# 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,
|
||||
ts.tm_min, ts.tm_sec)
|
||||
return dt.isoformat()
|
||||
|
||||
def add_items_tvcontacts(self, file_list, parentdir = None, parent = None):
|
||||
# TODO: execute this method inside of a thread
|
||||
for f in file_list:
|
||||
# Relative name to be used internally in the shared folders
|
||||
relative_name = f.split('/')[-1]
|
||||
if parentdir:
|
||||
relative_name = parentdir + '/' + relative_name
|
||||
short_name = relative_name.split('/')[-1]
|
||||
if short_name[0] == '.':
|
||||
# Return if it is a hidden file
|
||||
return
|
||||
if parent:
|
||||
row = self.ts_files.append(parent, (short_name,))
|
||||
else:
|
||||
row = self.ts_files.insert(None, 0, (short_name,))
|
||||
# File info
|
||||
size = os.path.getsize(f)
|
||||
is_dir = os.path.isdir(f)
|
||||
mod_date = os.path.getmtime(f)
|
||||
mod_date = self.__convert_date(mod_date)
|
||||
# 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_)
|
||||
except Exception, e:
|
||||
if e == 'Duplicated entry':
|
||||
print 'Error: ' + e
|
||||
continue
|
||||
else:
|
||||
raise Exception(e)
|
||||
if is_dir:
|
||||
tmpfl = os.listdir(f)
|
||||
fl = []
|
||||
for item in tmpfl:
|
||||
fl.append(f + '/' + item)
|
||||
self.add_items_tvcontacts(fl, relative_name, row)
|
||||
|
||||
def add_contact_browse(self, contact):
|
||||
fjid = contact.get_full_jid()
|
||||
jid = gajim.get_jid_without_resource(fjid)
|
||||
contacts = gajim.contacts.get_contacts(self.account, jid)
|
||||
for con in contacts:
|
||||
if con.show in ('offline', 'error') and not \
|
||||
con.supports(fshare_protocol.NS_FILE_SHARING):
|
||||
break
|
||||
cjid = con.get_full_jid()
|
||||
r = self.ts_search.insert(None, 0,
|
||||
(cjid, ))
|
||||
self.browse_jid[cjid] = r
|
||||
pro = fshare.FileSharePlugin.prohandler[self.account]
|
||||
# Request list of files from peer
|
||||
stanza = pro.request(cjid)
|
||||
if pro.conn.connection:
|
||||
pro.conn.connection.send(stanza)
|
||||
|
||||
def add_contact_manage(self, contact):
|
||||
self.contacts_rows = []
|
||||
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),
|
||||
(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,
|
||||
gajim.get_jid_without_resource(contact.get_full_jid())),
|
||||
self.ts_files
|
||||
)
|
||||
|
||||
def delete_event(self, widget, data=None):
|
||||
fshare.FileSharePlugin.filesharewindow = {}
|
||||
return False
|
||||
|
||||
def __check_combo_edit(self, widget, data=None):
|
||||
self.ts_files.clear()
|
||||
entry = self.cbb_contacts.child
|
||||
contact = entry.get_text()
|
||||
self.manage_vbox2.set_sensitive(False)
|
||||
for i in self.contacts_rows:
|
||||
# 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,
|
||||
contact),
|
||||
self.ts_files
|
||||
)
|
||||
self.manage_vbox2.set_sensitive(True)
|
||||
self.bt_remove.set_sensitive(False)
|
||||
break
|
||||
|
||||
def remove_file_clicked(self, widget, data=None):
|
||||
entry = self.cbb_contacts.child
|
||||
contact = entry.get_text()
|
||||
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)
|
||||
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.ts_files.clear()
|
||||
|
||||
def row_selected(self, widget, data=None):
|
||||
# When row is selected in tv_files
|
||||
sel = self.treeSelection_files.get_selected()
|
||||
if not sel[1]:
|
||||
return
|
||||
depth = self.ts_files.iter_depth(sel[1])
|
||||
# Don't remove file and dirs that aren't at the root level
|
||||
if depth == 0:
|
||||
self.bt_remove.set_sensitive(True)
|
||||
else:
|
||||
self.bt_remove.set_sensitive(False)
|
||||
|
||||
def row_expanded(self, widget, iter_, path, data=None):
|
||||
name = None
|
||||
for key in self.empty_row_child:
|
||||
parent = self.ts_search.iter_parent(self.empty_row_child[key])
|
||||
p = self.ts_search.get_path(parent)
|
||||
if p == path:
|
||||
name = key
|
||||
break
|
||||
if name:
|
||||
# if we found that the expanded row is the parent of the empty row
|
||||
# remove it from the treestore and empty_row dictionary. Then ask
|
||||
# peer for list of files of that directory
|
||||
i = self.empty_row_child[name]
|
||||
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,
|
||||
contact )
|
||||
stanza = pro.request(contact.get_full_jid(), name, isFile=False)
|
||||
if pro.conn.connection:
|
||||
pro.conn.connection.send(stanza)
|
||||
self.ts_search.remove(i)
|
||||
del self.empty_row_child[name]
|
||||
|
||||
def get_contact_from_iter(self, treestore, iter_):
|
||||
toplevel = treestore.get_iter_root()
|
||||
while toplevel:
|
||||
if treestore.is_ancestor(toplevel, iter_):
|
||||
return treestore.get_value(toplevel, 0)
|
||||
toplevel = treestore.iter_next(toplevel)
|
||||
|
||||
def on_treeview_button_press_event(self, treeview, event):
|
||||
if event.button == 3:
|
||||
x = int(event.x)
|
||||
y = int(event.y)
|
||||
time = event.time
|
||||
pthinfo = treeview.get_path_at_pos(x, y)
|
||||
if pthinfo is not None:
|
||||
path, col, cellx, celly = pthinfo
|
||||
treeview.grab_focus()
|
||||
treeview.set_cursor( path, col, 0)
|
||||
treestore = treeview.get_model()
|
||||
it = treestore.get_iter(path)
|
||||
if treestore.iter_depth(it) != 0:
|
||||
self.browse_popup.popup(None, None, None, event.button, time)
|
||||
return True
|
||||
|
||||
def on_bt_search_clicked(self, widget, data=None):
|
||||
pass
|
||||
|
||||
def on_download_clicked(self, widget, data=None):
|
||||
tree, row = self.tv_search.get_selection().get_selected()
|
||||
path = tree.get_path(row)
|
||||
file_info = self.brw_file_info[path]
|
||||
fjid = self.get_contact_from_iter(tree, row)
|
||||
# Request the file
|
||||
file_path = config.INCOMING_DIR + '/%s' % file_info[0]
|
||||
sid = helpers.get_random_string_16()
|
||||
new_file_props = FilesProp.getNewFileProp(self.account, sid)
|
||||
new_file_props.file_name = file_path
|
||||
print file_path
|
||||
new_file_props.name = file_info[0]
|
||||
new_file_props.desc = file_info[4]
|
||||
new_file_props.size = file_info[2]
|
||||
new_file_props.date = file_info[1]
|
||||
new_file_props.hash_ = None if file_info[3] == '' else file_info[3]
|
||||
new_file_props.type_ = 'r'
|
||||
tsid = gajim.connections[self.account].start_file_transfer(fjid,
|
||||
new_file_props,
|
||||
True)
|
||||
new_file_props.transport_sid = tsid
|
||||
ft_window = gajim.interface.instances['file_transfers']
|
||||
contact = gajim.contacts.get_contact_from_full_jid(self.account, fjid)
|
||||
ft_window .add_transfer(self.account, contact, new_file_props)
|
||||
|
||||
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,
|
||||
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,
|
||||
gtk.STOCK_OPEN,gtk.RESPONSE_OK))
|
||||
response = chooser.run()
|
||||
if response == gtk.RESPONSE_OK:
|
||||
file_name = chooser.get_filename()
|
||||
self.entry_dir_pref.set_text(file_name)
|
||||
config.set('incoming_dir', file_name)
|
||||
chooser.destroy()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
f = FileShareWindow()
|
||||
f.show()
|
||||
gtk.main()
|
||||
|
||||
159
file_sharing/fshare.py
Normal file
159
file_sharing/fshare.py
Normal file
@@ -0,0 +1,159 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##
|
||||
import database
|
||||
import gtk
|
||||
import base64
|
||||
import urllib2
|
||||
from plugins import GajimPlugin
|
||||
from plugins.helpers import log_calls
|
||||
import gui_menu_builder
|
||||
import gtkgui_helpers
|
||||
from common import gajim
|
||||
from fileshare_window import FileShareWindow
|
||||
import fshare_protocol
|
||||
from common import ged
|
||||
from common import caps_cache
|
||||
from common import xmpp
|
||||
|
||||
|
||||
class FileSharePlugin(GajimPlugin):
|
||||
|
||||
filesharewindow = {}
|
||||
prohandler = {}
|
||||
|
||||
@log_calls('FileSharePlugin')
|
||||
def init(self):
|
||||
self.activated = False
|
||||
self.description = _('This plugin allows you to share folders'+
|
||||
' with a peer using jingle file transfer.')
|
||||
self.config_dialog = None
|
||||
# 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)
|
||||
self.events_handlers = {
|
||||
'raw-iq-received': (ged.CORE, self._nec_raw_iq)
|
||||
}
|
||||
|
||||
def activate(self):
|
||||
self.activated = True
|
||||
# Add fs feature
|
||||
if fshare_protocol.NS_FILE_SHARING not in gajim.gajim_common_features:
|
||||
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
|
||||
gui_menu_builder.get_contact_menu = self.contact_menu
|
||||
# Replace get_file_info
|
||||
for account in gajim.contacts.get_accounts():
|
||||
conn = gajim.connections[account]
|
||||
self._get_file_info = conn.get_file_info
|
||||
conn.get_file_info = self.get_file_info
|
||||
|
||||
def deactivate(self):
|
||||
self.activated = False
|
||||
# Remove fs feature
|
||||
if fshare_protocol.NS_FILE_SHARING not in gajim.gajim_common_features:
|
||||
gajim.gajim_common_features.remove(fshare_protocol.NS_FILE_SHARING)
|
||||
self._compute_caps_hash()
|
||||
# Restore the contact menu
|
||||
gui_menu_builder.get_contact_menu = self.__get_contact_menu
|
||||
# Restore get_file_info
|
||||
for account in gajim.contacts.get_accounts():
|
||||
conn = gajim.connections[account]
|
||||
conn.get_file_info = self._get_file_info
|
||||
|
||||
def _compute_caps_hash(self):
|
||||
for a in gajim.connections:
|
||||
gajim.caps_hash[a] = caps_cache.compute_caps_hash([
|
||||
gajim.gajim_identity], gajim.gajim_common_features + \
|
||||
gajim.gajim_optional_features[a])
|
||||
# re-send presence with new hash
|
||||
connected = gajim.connections[a].connected
|
||||
if connected > 1 and gajim.SHOW_LIST[connected] != 'invisible':
|
||||
gajim.connections[a].change_status(gajim.SHOW_LIST[connected],
|
||||
gajim.connections[a].status)
|
||||
|
||||
def _nec_raw_iq(self, obj):
|
||||
if obj.stanza.getTag('match',
|
||||
namespace=fshare_protocol.NS_FILE_SHARING) and self.activated:
|
||||
account = obj.conn.name
|
||||
pro = FileSharePlugin.prohandler[account]
|
||||
pro.handler(obj.stanza)
|
||||
raise xmpp.NodeProcessed
|
||||
|
||||
def __get_contact_menu(self, contact, account):
|
||||
raise NotImplementedError
|
||||
|
||||
def contact_menu(self, contact, account):
|
||||
menu = self.__get_contact_menu(contact, account)
|
||||
fs = gtk.MenuItem('File sharing')
|
||||
submenu = gtk.Menu()
|
||||
fs.set_submenu(submenu)
|
||||
bf = gtk.MenuItem('Browse files')
|
||||
bf.connect('activate', self.browse_menu_clicked, account, contact)
|
||||
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)
|
||||
if gajim.account_is_disconnected(account) or \
|
||||
contact.show in ('offline', 'error') or not \
|
||||
contact.supports(fshare_protocol.NS_FILE_SHARING):
|
||||
bf.set_sensitive(False)
|
||||
submenu.show()
|
||||
bf.show()
|
||||
msf.show()
|
||||
enable_fs.show()
|
||||
fs.show()
|
||||
menu.attach(fs, 0, 1, 3, 4)
|
||||
return menu
|
||||
|
||||
def _get_file_info(self, peerjid, hash_=None, name=None, account=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_file_info(self, peerjid, hash_=None, name=None, account=None):
|
||||
file_info = self._get_file_info(hash_, name)
|
||||
if file_info:
|
||||
return file_info
|
||||
raw_info = 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
|
||||
}
|
||||
|
||||
return file_info
|
||||
|
||||
def __get_fsw_instance(self, account):
|
||||
# Makes sure we only have one instance of the window per account
|
||||
if account not in FileSharePlugin.filesharewindow:
|
||||
FileSharePlugin.filesharewindow[account] = fsw = FileShareWindow()
|
||||
FileSharePlugin.prohandler[account].set_window(fsw)
|
||||
return FileSharePlugin.filesharewindow[account]
|
||||
|
||||
def __init_window(self, account, contact):
|
||||
fsw = self.__get_fsw_instance(account)
|
||||
fsw.set_account(account)
|
||||
fsw.contacts_rows = []
|
||||
fsw.ts_contacts.clear()
|
||||
fsw.ts_search.clear()
|
||||
# Add information to widgets
|
||||
fsw.add_contact_manage(contact)
|
||||
fsw.add_contact_browse(contact)
|
||||
return fsw
|
||||
|
||||
def manage_menu_clicked(self, widget, account, contact):
|
||||
fsw = self.__init_window(account, contact)
|
||||
fsw.notebook.set_current_page(1)
|
||||
|
||||
def browse_menu_clicked(self, widget, account, contact):
|
||||
fsw = self.__init_window(account, contact)
|
||||
fsw.notebook.set_current_page(0)
|
||||
|
||||
|
||||
163
file_sharing/fshare_protocol.py
Normal file
163
file_sharing/fshare_protocol.py
Normal file
@@ -0,0 +1,163 @@
|
||||
from common import xmpp
|
||||
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():
|
||||
|
||||
def __init__(self, account):
|
||||
self.account = account
|
||||
self.conn = gajim.connections[self.account]
|
||||
# get our jid with resource
|
||||
self.ourjid = gajim.get_jid_from_account(self.account)
|
||||
self.fsw = None
|
||||
|
||||
def set_window(self, window):
|
||||
self.fsw = window
|
||||
|
||||
def request(self, contact, name=None, isFile=False):
|
||||
iq = xmpp.Iq(typ='get', to=contact, frm=self.ourjid)
|
||||
match = iq.addChild(name='match', namespace=NS_FILE_SHARING)
|
||||
request = match.addChild(name='request')
|
||||
if not isFile and name is None:
|
||||
request.addChild(name='directory')
|
||||
elif not isFile and name is not None:
|
||||
dir_ = request.addChild(name='directory')
|
||||
dir_.addChild(name='name').addData('/' + name)
|
||||
elif isFile:
|
||||
pass
|
||||
return iq
|
||||
|
||||
def __buildReply(self, typ, stanza):
|
||||
iq = xmpp.Iq(typ, to=stanza.getFrom(), frm=stanza.getTo(),
|
||||
attrs={'id': stanza.getID()})
|
||||
iq.addChild(name='match', namespace=NS_FILE_SHARING)
|
||||
return iq
|
||||
|
||||
def on_request(self, stanza):
|
||||
try:
|
||||
fjid = helpers.get_full_jid_from_iq(stanza)
|
||||
except helpers.InvalidFormat:
|
||||
# A message from a non-valid JID arrived, it has been ignored.
|
||||
return
|
||||
if stanza.getTag('error'):
|
||||
# TODO: better handle this
|
||||
return
|
||||
jid = gajim.get_jid_without_resource(fjid)
|
||||
req = stanza.getTag('match').getTag('request')
|
||||
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)
|
||||
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_)
|
||||
response = self.offer(stanza.getID(), fjid, files)
|
||||
self.conn.connection.send(response)
|
||||
|
||||
def on_offer(self, stanza):
|
||||
# We just got a stanza offering files
|
||||
fjid = helpers.get_full_jid_from_iq(stanza)
|
||||
info = get_files_info(stanza)
|
||||
if fjid not in self.fsw.browse_jid or not info:
|
||||
# We weren't expecting anything from this contact, do nothing
|
||||
# Or we didn't receive any offering files
|
||||
return
|
||||
flist = []
|
||||
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.browse_jid[fjid]
|
||||
)
|
||||
for f in info[0]:
|
||||
iter_ = self.fsw.browse_fref[f['name']]
|
||||
path = self.fsw.ts_search.get_path(iter_)
|
||||
self.fsw.brw_file_info[path] = (f['name'], f['date'], f['size'],
|
||||
f['hash'], f['desc'])
|
||||
|
||||
# TODO: add tooltip
|
||||
'''
|
||||
for f in info[0]:
|
||||
r = self.fsw.browse_fref[f['name']]
|
||||
path = self.fsw.ts_search.get_path(r)
|
||||
# AM HERE WORKING ON THE TOOLTIP
|
||||
tooltip.set_text('noooo')
|
||||
self.fsw.tv_search.set_tooltip_row(tooltip, path)
|
||||
'''
|
||||
for dir_ in info[1]:
|
||||
if dir_ not in self.fsw.empty_row_child:
|
||||
parent = self.fsw.browse_fref[dir_]
|
||||
row = self.fsw.ts_search.append(parent, ('',))
|
||||
self.fsw.empty_row_child[dir_] = row
|
||||
|
||||
def handler(self, stanza):
|
||||
# handles incoming match stanza
|
||||
if stanza.getTag('match').getTag('offer'):
|
||||
self.on_offer(stanza)
|
||||
elif stanza.getTag('match').getTag('request'):
|
||||
self.on_request(stanza)
|
||||
else:
|
||||
# TODO: reply with malformed stanza error
|
||||
pass
|
||||
|
||||
def offer(self, id_, contact, items):
|
||||
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')
|
||||
if len(items) == 0:
|
||||
offer.addChild(name='directory')
|
||||
else:
|
||||
for i in items:
|
||||
# if it is a directory
|
||||
if i[5] == True:
|
||||
item = offer.addChild(name='directory')
|
||||
name = item.addChild('name')
|
||||
name.setData('/' + i[0])
|
||||
else:
|
||||
item = offer.addChild(name='file')
|
||||
item.addChild('name').setData('/' + i[0])
|
||||
if i[1] != '':
|
||||
h = Hashes()
|
||||
h.addHash(i[1], 'sha-1')
|
||||
item.addChild(node=h)
|
||||
item.addChild('size').setData(i[2])
|
||||
item.addChild('desc').setData(i[3])
|
||||
item.addChild('date').setData(i[4])
|
||||
return iq
|
||||
|
||||
def set_window(self, fsw):
|
||||
self.fsw = fsw
|
||||
|
||||
|
||||
def get_files_info(stanza):
|
||||
# Crawls the stanza in search for file and dir structure.
|
||||
files = []
|
||||
dirs = []
|
||||
children = stanza.getTag('match').getTag('offer').getChildren()
|
||||
for c in children:
|
||||
if c.getName() == 'file':
|
||||
f = {'name' : \
|
||||
c.getTag('name').getData()[1:] if c.getTag('name') else '',
|
||||
'size' : c.getTag('size').getData() if c.getTag('size') else '',
|
||||
'date' : c.getTag('date').getData() if c.getTag('date') else '',
|
||||
'desc' : c.getTag('desc').getData() if c.getTag('desc') else '',
|
||||
# TODO: handle different hash algo
|
||||
'hash' : c.getTag('hash').getData() if c.getTag('hash') else '',
|
||||
}
|
||||
files.append(f)
|
||||
else:
|
||||
dirname = c.getTag('name')
|
||||
if dirname is None:
|
||||
return None
|
||||
dirs.append(dirname.getData()[1:])
|
||||
return (files, dirs)
|
||||
|
||||
8
file_sharing/manifest.ini
Normal file
8
file_sharing/manifest.ini
Normal file
@@ -0,0 +1,8 @@
|
||||
[info]
|
||||
name: File Sharing
|
||||
short_name: fshare
|
||||
#version: 0.1
|
||||
description: This plugin allows you to share folders with your peers using jingle file transfer.
|
||||
authors: Jefry Lagrange <jefry.reyes@gmail.com>
|
||||
homepage: www.google.com
|
||||
|
||||
Reference in New Issue
Block a user