412 lines
18 KiB
Python
412 lines
18 KiB
Python
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()
|
|
|