Commit f9271e98 authored by zimio's avatar zimio

initial commit for file_share plugin

parent 511b37dd
# use glob syntax.
syntax: glob
*.pyc
*.pyo
*.db
from fshare import FileSharePlugin
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')
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()
This diff is collapsed.
# -*- 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)
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)
[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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment