Commit 66d357d6 authored by Saul Ibarra's avatar Saul Ibarra

Add file transfer support

parent 3bc1b5e1
......@@ -5,6 +5,7 @@
__all__ = ['BlinkSettings', 'SIPSimpleSettingsExtension']
import os
import platform
import sys
......@@ -42,8 +43,7 @@ class ChatSettingsExtension(ChatSettings):
class FileTransferSettingsExtension(FileTransferSettings):
auto_accept = Setting(type=bool, default=False)
directory = Setting(type=Path, default=None, nillable=True)
directory = Setting(type=Path, default=Path(os.path.expanduser('~/Downloads')))
class GoogleContactsSettings(SettingsGroup):
......
This diff is collapsed.
# Copyright (C) 2014 AG Projects. See LICENSE for details.
#
__all__ = ['FileTransferWindow']
import os
from PyQt4 import uic
from PyQt4.QtCore import Qt, QUrl
from PyQt4.QtGui import QAction, QDesktopServices, QMenu
from application.notification import IObserver, NotificationCenter
from application.python import Null
from application.system import makedirs
from zope.interface import implements
from sipsimple.configuration.settings import SIPSimpleSettings
from blink.resources import Resources
from blink.sessions import FileTransferDelegate, FileTransferModel
from blink.widgets.util import ContextMenuActions
ui_class, base_class = uic.loadUiType(Resources.get('filetransfer_window.ui'))
class FileTransferWindow(base_class, ui_class):
implements(IObserver)
def __init__(self, parent=None):
super(FileTransferWindow, self).__init__(parent)
with Resources.directory:
self.setupUi(self)
self.model = FileTransferModel(self)
self.listview.setModel(self.model)
self.listview.setItemDelegate(FileTransferDelegate(self.listview))
self.listview.customContextMenuRequested.connect(self._SH_ContextMenuRequested)
self.context_menu = QMenu(self.listview)
self.actions = ContextMenuActions()
self.actions.open_file = QAction("Open", self, triggered=self._AH_OpenFile)
self.actions.open_folder = QAction("Open Containing Folder", self, triggered=self._AH_OpenContainingFolder)
self.actions.cancel_transfer = QAction("Cancel", self, triggered=self._AH_CancelTransfer)
self.actions.remove_entry = QAction("Remove From List", self, triggered=self._AH_RemoveEntry)
self.actions.open_downloads_folder = QAction("Open Downloads Folder", self, triggered=self._AH_OpenDownloadsFolder)
self.actions.clear_list = QAction("Clear List", self, triggered=self._AH_ClearList)
self.model.itemAdded.connect(self.update_status)
self.model.itemRemoved.connect(self.update_status)
notification_center = NotificationCenter()
notification_center.add_observer(self, name='FileTransferDidEnd')
def show(self, activate=True):
settings = SIPSimpleSettings()
directory = settings.file_transfer.directory.normalized
makedirs(directory)
self.setAttribute(Qt.WA_ShowWithoutActivating, not activate)
super(FileTransferWindow, self).show()
self.raise_()
if activate:
self.activateWindow()
def update_status(self):
total = len(self.model.items)
active = len([item for item in self.model.items if not item.ended])
text = u'%d %s' % (total, 'transfer' if total==1 else 'transfers')
if active > 0:
text += u' (%d active)' % active
self.status_label.setText(text)
def handle_notification(self, notification):
handler = getattr(self, '_NH_%s' % notification.name, Null)
handler(notification)
def _NH_FileTransferDidEnd(self, notification):
self.update_status()
def _SH_ContextMenuRequested(self, pos):
menu = self.context_menu
menu.clear()
index = self.listview.indexAt(pos)
if index.isValid():
item = index.data(Qt.UserRole)
if item.ended:
if item.direction == 'incoming' and item.ended and not item.failed:
menu.addAction(self.actions.open_file)
menu.addAction(self.actions.open_folder)
menu.addAction(self.actions.remove_entry)
else:
menu.addAction(self.actions.cancel_transfer)
menu.addSeparator()
menu.addAction(self.actions.open_downloads_folder)
menu.addAction(self.actions.clear_list)
elif self.model.rowCount() > 0:
menu.addAction(self.actions.open_downloads_folder)
menu.addAction(self.actions.clear_list)
else:
menu.addAction(self.actions.open_downloads_folder)
menu.exec_(self.mapToGlobal(pos))
def _AH_OpenFile(self):
item = self.listview.selectedIndexes()[0].data(Qt.UserRole)
QDesktopServices.openUrl(QUrl.fromLocalFile(item.filename))
def _AH_OpenContainingFolder(self):
item = self.listview.selectedIndexes()[0].data(Qt.UserRole)
QDesktopServices.openUrl(QUrl.fromLocalFile(os.path.basename(item.filename)))
def _AH_CancelTransfer(self):
item = self.listview.selectedIndexes()[0].data(Qt.UserRole)
item.end()
def _AH_RemoveEntry(self):
item = self.listview.selectedIndexes()[0].data(Qt.UserRole)
self.model.removeItem(item)
def _AH_OpenDownloadsFolder(self):
settings = SIPSimpleSettings()
directory = settings.file_transfer.directory.normalized
QDesktopServices.openUrl(QUrl.fromLocalFile(directory))
def _AH_ClearList(self):
self.model.clear_ended()
del ui_class, base_class
......@@ -25,6 +25,7 @@ from sipsimple.configuration.settings import SIPSimpleSettings
from blink.aboutpanel import AboutPanel
from blink.accounts import AccountModel, ActiveAccountModel, ServerToolsAccountModel, ServerToolsWindow
from blink.contacts import Contact, ContactEditorDialog, ContactModel, ContactSearchModel, GoogleContactsDialog, URIUtils
from blink.filetransferwindow import FileTransferWindow
from blink.history import HistoryManager
from blink.preferences import PreferencesWindow
from blink.sessions import ConferenceDialog, SessionManager, AudioSessionModel, StreamDescription
......@@ -52,6 +53,8 @@ class MainWindow(base_class, ui_class):
notification_center.add_observer(self, name='SIPAccountGotPendingWatcher')
notification_center.add_observer(self, name='BlinkSessionNewOutgoing')
notification_center.add_observer(self, name='BlinkSessionDidReinitializeForOutgoing')
notification_center.add_observer(self, name='FileTransferNewIncoming')
notification_center.add_observer(self, name='FileTransferNewOutgoing')
notification_center.add_observer(self, sender=AccountManager())
icon_manager = IconManager()
......@@ -122,6 +125,7 @@ class MainWindow(base_class, ui_class):
self.conference_dialog = ConferenceDialog(self)
self.contact_editor_dialog = ContactEditorDialog(self)
self.google_contacts_dialog = GoogleContactsDialog(self)
self.filetransfer_window = FileTransferWindow()
self.preferences_window = PreferencesWindow(self.account_model, None)
self.server_tools_window = ServerToolsWindow(self.server_tools_account_model, None)
......@@ -172,7 +176,6 @@ class MainWindow(base_class, ui_class):
self.help_action.triggered.connect(partial(QDesktopServices.openUrl, QUrl(u'http://icanblink.com/help-qt.phtml')))
self.preferences_action.triggered.connect(self.preferences_window.show)
self.auto_accept_chat_action.triggered.connect(self._AH_AutoAcceptChatTriggered)
self.auto_accept_files_action.triggered.connect(self._AH_AutoAcceptFilesTriggered)
self.release_notes_action.triggered.connect(partial(QDesktopServices.openUrl, QUrl(u'http://icanblink.com/changelog-qt.phtml')))
self.quit_action.triggered.connect(self._AH_QuitActionTriggered)
......@@ -202,6 +205,7 @@ class MainWindow(base_class, ui_class):
self.search_for_people_action.triggered.connect(self._AH_SearchForPeople)
self.history_on_server_action.triggered.connect(self._AH_HistoryOnServer)
self.buy_pstn_access_action.triggered.connect(self._AH_PurchasePstnAccess)
self.file_transfers_action.triggered.connect(self._AH_FileTransfersActionTriggered)
self.logs_action.triggered.connect(self._AH_LogsActionTriggered)
def setupUi(self):
......@@ -235,6 +239,7 @@ class MainWindow(base_class, ui_class):
self.conference_dialog.close()
self.contact_editor_dialog.close()
self.google_contacts_dialog.close()
self.filetransfer_window.close()
self.preferences_window.close()
self.server_tools_window.close()
for dialog in self.pending_watcher_dialogs[:]:
......@@ -324,11 +329,6 @@ class MainWindow(base_class, ui_class):
settings.chat.auto_accept = checked
settings.save()
def _AH_AutoAcceptFilesTriggered(self, checked):
settings = SIPSimpleSettings()
settings.file_transfer.auto_accept = checked
settings.save()
def _AH_EnableAnsweringMachineTriggered(self, checked):
settings = SIPSimpleSettings()
settings.answering_machine.enabled = checked
......@@ -369,6 +369,9 @@ class MainWindow(base_class, ui_class):
account = account if account is not BonjourAccount() and account.server.settings_url else None
self.server_tools_window.open_buy_pstn_access_page(account)
def _AH_FileTransfersActionTriggered(self, checked):
self.filetransfer_window.show()
def _AH_LogsActionTriggered(self, checked):
directory = ApplicationData.get('logs')
makedirs(directory)
......@@ -662,7 +665,6 @@ class MainWindow(base_class, ui_class):
self.silent_button.setChecked(settings.audio.silent)
self.answering_machine_action.setChecked(settings.answering_machine.enabled)
self.auto_accept_chat_action.setChecked(settings.chat.auto_accept)
self.auto_accept_files_action.setChecked(settings.file_transfer.auto_accept)
if settings.google_contacts.authorization_token is None:
self.google_contacts_action.setText(u'Enable Google Contacts')
else:
......@@ -728,8 +730,6 @@ class MainWindow(base_class, ui_class):
self.answering_machine_action.setChecked(settings.answering_machine.enabled)
if 'chat.auto_accept' in notification.data.modified:
self.auto_accept_chat_action.setChecked(settings.chat.auto_accept)
if 'file_transfer.auto_accept' in notification.data.modified:
self.auto_accept_files_action.setChecked(settings.file_transfer.auto_accept)
if 'google_contacts.authorization_token' in notification.data.modified:
authorization_token = notification.sender.google_contacts.authorization_token
if authorization_token is None:
......@@ -816,6 +816,11 @@ class MainWindow(base_class, ui_class):
def _NH_BlinkSessionDidReinitializeForOutgoing(self, notification):
self.search_box.clear()
def _NH_FileTransferNewIncoming(self, notification):
self.filetransfer_window.show(activate=QApplication.activeWindow() is not None)
def _NH_FileTransferNewOutgoing(self, notification):
self.filetransfer_window.show(activate=QApplication.activeWindow() is not None)
del ui_class, base_class
......
......@@ -240,7 +240,6 @@ class PreferencesWindow(base_class, ui_class):
self.sms_replication_button.clicked.connect(self._SH_SMSReplicationButtonClicked)
# File transfer
self.auto_accept_files_button.clicked.connect(self._SH_AutoAcceptFilesButtonClicked)
self.download_directory_editor.locationCleared.connect(self._SH_DownloadDirectoryEditorLocationCleared)
self.download_directory_browse_button.clicked.connect(self._SH_DownloadDirectoryBrowseButtonClicked)
......@@ -501,7 +500,6 @@ class PreferencesWindow(base_class, ui_class):
self.sms_replication_button.setChecked(settings.chat.sms_replication)
# File transfer settings
self.auto_accept_files_button.setChecked(settings.file_transfer.auto_accept)
self.download_directory_editor.setText(settings.file_transfer.directory or u'')
# Alert settings
......@@ -1091,11 +1089,6 @@ class PreferencesWindow(base_class, ui_class):
settings.save()
# File transfer signal handlers
def _SH_AutoAcceptFilesButtonClicked(self, checked):
settings = SIPSimpleSettings()
settings.file_transfer.auto_accept = checked
settings.save()
def _SH_DownloadDirectoryEditorLocationCleared(self):
settings = SIPSimpleSettings()
settings.file_transfer.directory = None
......@@ -1299,8 +1292,6 @@ class PreferencesWindow(base_class, ui_class):
self.enable_answering_machine_button.setChecked(settings.answering_machine.enabled)
if 'chat.auto_accept' in notification.data.modified:
self.auto_accept_chat_button.setChecked(settings.chat.auto_accept)
if 'file_transfer.auto_accept' in notification.data.modified:
self.auto_accept_files_button.setChecked(settings.file_transfer.auto_accept)
elif isinstance(notification.sender, (Account, BonjourAccount)) and notification.sender is self.selected_account:
account = notification.sender
if 'enabled' in notification.data.modified:
......
This diff is collapsed.
......@@ -948,7 +948,6 @@ padding: 2px;</string>
<normaloff>icons/quick-settings.png</normaloff>icons/quick-settings.png</iconset>
</property>
<addaction name="auto_accept_chat_action"/>
<addaction name="auto_accept_files_action"/>
</widget>
<addaction name="about_action"/>
<addaction name="check_for_updates_action"/>
......@@ -1139,9 +1138,6 @@ padding: 2px;</string>
</property>
</action>
<action name="file_transfers_action">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;File transfers</string>
</property>
......@@ -1217,14 +1213,6 @@ padding: 2px;</string>
<string>Auto-accept &amp;chat from my contacts</string>
</property>
</action>
<action name="auto_accept_files_action">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Auto-accept &amp;files from my contacts</string>
</property>
</action>
<action name="add_account_action">
<property name="text">
<string>&amp;Add account...</string>
......
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>filetransfer_item</class>
<widget class="QWidget" name="filetransfer_item">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>300</width>
<height>55</height>
</rect>
</property>
<property name="palette">
<palette>
<active>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Highlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>102</green>
<blue>204</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>240</red>
<green>244</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Highlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>102</green>
<blue>204</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>240</red>
<green>244</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Highlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>218</red>
<green>216</green>
<blue>213</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>240</red>
<green>244</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="windowTitle">
<string>FileTransfer Item</string>
</property>
<layout class="QHBoxLayout" name="widget_layout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>1</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>1</number>
</property>
<item>
<widget class="QLabel" name="icon_label">
<property name="minimumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="pixmap">
<pixmap>icons/folder-downloads.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="file_info_layout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="ElidedLabel" name="filename_label">
<property name="text">
<string>test_file.py</string>
</property>
<property name="margin">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="ElidedLabel" name="name_label">
<property name="text">
<string>test@example.com</string>
</property>
<property name="margin">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="ElidedLabel" name="status_label">
<property name="text">
<string>Initializing...</string>
</property>
<property name="margin">
<number>1</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="TransferStateLabel" name="state_indicator">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>16</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ElidedLabel</class>
<extends>QLabel</extends>
<header>blink.widgets.labels</header>
</customwidget>
<customwidget>
<class>TransferStateLabel</class>
<extends>QLabel</extends>
<header>blink.sessions</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>filetransfer_window</class>
<widget class="QWidget" name="filetransfer_window">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>350</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>280</height>
</size>
</property>
<property name="windowTitle">
<string>File Transfers</string>
</property>
<property name="windowIcon">
<iconset>
<normaloff>icons/blink48.png</normaloff>icons/blink48.png</iconset>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QListView" name="listview">
<property name="mouseTracking">
<bool>true</bool>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="styleSheet">
<string notr="true">QListView { border: 1px solid palette(dark); border-style: inset; border-radius: 3px; }</string>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="status_label">
<property name="text">
<string>0 transfers</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="margin">
<number>4</number>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
This diff is collapsed.
......@@ -1948,14 +1948,20 @@
<property name="horizontalSpacing">
<number>3</number>
</property>
<item row="0" column="1" colspan="2">
<widget class="QCheckBox" name="auto_accept_files_button">
<property name="text">
<string>Automatically accept file transfer requests from known contacts</string>
<item row="1" column="0" colspan="3">
<spacer name="file_transfer_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<item row="0" column="0">
<widget class="QLabel" name="download_directory_label">
<property name="text">
<string>Download Directory:</string>
......@@ -1965,33 +1971,20 @@
</property>
</widget>
</item>
<item row="1" column="1">
<item row="0" column="1">
<widget class="LocationBar" name="download_directory_editor">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<item row="0" column="2">
<widget class="QPushButton" name="download_directory_browse_button">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="3">
<spacer name="file_transfer_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
......
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