8b89ae8a79cddc72a211470f17fa993220371114
[qtrapids] / src / client / MainWindow.cpp
1 /***************************************************************************
2  *   Copyright (C) 2009 by Lassi Väätämöinen   *
3  *   lassi.vaatamoinen@ixonos.com   *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20
21
22 #include <qtrapids/dbus.hpp>
23
24 #include <QDebug>
25 #include <QtGui/QMenuBar>
26 #include <QtGui/QToolBar>
27 #include <QAction>
28 #include <QFileDialog>
29 #include <QMessageBox>
30 #include <QApplication>
31 #include <QPluginLoader>
32
33 #include "DownloadView.h"
34 #include "SeedView.h"
35 #include "PreferencesDialog.h"
36
37 #include "MainWindow.h"
38
39 namespace qtrapids
40 {
41
42 const QString ABOUT_TEXT
43 = QString(QObject::trUtf8("QtRapids, a simple BitTorrent client based on"
44                           "\nQt and Libtorrent."
45                           "\n\nURL: http://qtrapids.garage.maemo.org/"
46                           "\n\nAuthors:\nLassi Väätämöinen, lassi.vaatamoinen@ixonos.com"
47                           "\nDenis Zalevskiy, denis.zalewsky@ixonos.com"
48                           "\n\nIxonos Plc, Finland\n"));
49
50 const QString PLUGINS_DIR = "plugins";
51
52 // Consturctor
53 MainWindow::MainWindow() :
54                 QMainWindow(), // Superclass
55                 tabWidget_(NULL),
56                 dlView_(NULL),
57                 seedView_(NULL),
58                 searchWidget_(NULL),
59                 preferencesDialog_(NULL),
60                 settings_(QCoreApplication::organizationName()
61                           , QCoreApplication::applicationName()),
62                 pluginDirs_(),
63                 server_(QtRapidsServer::staticInterfaceName()
64                         , "/qtrapids", QDBusConnection::sessionBus())
65                 //      torrentHandles_(),
66 {
67         // MENUBAR
68         QMenuBar *menuBar = new QMenuBar();
69         QMenu *tempMenu = NULL;
70
71         tempMenu = menuBar->addMenu(tr("&File"));
72         QAction *openAction = tempMenu->addAction(tr("&Open"));
73         QAction *removeAction = tempMenu->addAction(tr("&Remove"));
74         removeAction->setEnabled(false);
75         QAction *quitAction = tempMenu->addAction(tr("&Quit"));
76
77         tempMenu = menuBar->addMenu(tr("&Settings"));
78         QAction *preferencesAction = tempMenu->addAction(tr("&Preferences"));
79
80         tempMenu = menuBar->addMenu(tr("&Help"));
81         QAction *aboutAction = tempMenu->addAction(tr("&About"));
82         QAction *aboutQtAction = tempMenu->addAction(tr("About &Qt"));
83
84         setMenuBar(menuBar);
85         connect(openAction, SIGNAL(triggered()), this, SLOT(on_openAction_clicked()));
86         connect(removeAction, SIGNAL(triggered()), this, SLOT(on_removeAction_clicked()));
87         connect(this, SIGNAL(itemSelected(bool)), removeAction, SLOT(setEnabled(bool)));
88         connect(quitAction, SIGNAL(triggered()), this, SLOT(on_quitAction_clicked()));
89         connect(preferencesAction, SIGNAL(triggered()), this, SLOT(on_preferencesAction_clicked()));
90         connect(aboutAction, SIGNAL(triggered()), this, SLOT(on_aboutAction_clicked()));
91         connect(aboutQtAction, SIGNAL(triggered()), this, SLOT(on_aboutQtAction_clicked()));
92
93         // TABWIDGET (central widget)
94         tabWidget_ = new QTabWidget();
95         tabWidget_->setTabsClosable(true);
96
97         /// @todo Exception handling
98         dlView_ = new DownloadView(this);
99         seedView_ = new SeedView(this);
100         tabWidget_->addTab(dlView_, tr("Downloads"));
101         tabWidget_->addTab(seedView_, tr("Seeds"));
102         connect(dlView_, SIGNAL(itemSelectionChanged()), this,
103                 SLOT(on_downloadItemSelectionChanged()));
104
105         connect(seedView_, SIGNAL(itemSelectionChanged()), this,
106                 SLOT(on_seedItemSelectionChanged()));
107
108         // Tab widget as central widget.
109         setCentralWidget(tabWidget_);
110
111         // TOOLBAR
112         QToolBar *toolBar = new QToolBar();
113         toolBar->addAction(tr("Open"));
114         removeAction = toolBar->addAction(tr("Remove"));
115         removeAction->setEnabled(false);
116         addToolBar(Qt::TopToolBarArea, toolBar);
117
118         connect(this, SIGNAL(itemSelected(bool)), removeAction,
119                 SLOT(setEnabled(bool)));
120         connect(toolBar, SIGNAL(actionTriggered(QAction*)), this,
121                 SLOT(handleToolBarAction(QAction*)));
122         connect (tabWidget_, SIGNAL(tabCloseRequested(int)), this, SLOT(on_tabWidget_tabCloseRequested(int)));
123
124         connect(&server_, SIGNAL(alert(qtrapids::TorrentState, qtrapids::ParamsMap_t)), 
125                                         this, SLOT(on_alert(qtrapids::TorrentState, qtrapids::ParamsMap_t)));
126                                         
127 //      connect(&btSession_, SIGNAL(alert(std::auto_ptr<Alert>)),
128 //              this, SLOT(on_alert(std::auto_ptr<Alert>)));
129
130
131         LoadPlugins();
132
133 }
134
135
136 MainWindow::~MainWindow()
137 {
138         settings_.setValue("geometry", saveGeometry());
139 }
140
141 // ===================== Implements PluginInterface =========================
142 /// @todo add PluginInterface parameter to request plugin name 
143 bool MainWindow::setGui(QWidget* widget, PluginWidgetType type, qtrapids::PluginInterface* plugin)
144 {
145 #ifdef QTRAPIDS_DEBUG
146         qDebug() << "MainWindow::setGui():" << dlView_->currentItem();
147 #endif
148
149         if (plugin && plugin->identifier() == "SearchPlugin") {
150                 searchWidget_ = widget;
151         } else {
152                 return false;
153         }
154         
155         tabWidget_->addTab(widget, tr("Search"));
156         return true;
157 }
158
159 /// @todo Add PluginInterface parameter to check which plugin gives the widget, to handle appropriately
160 void MainWindow::addPluginWidget(QWidget* widget, PluginWidgetType type)
161 {
162 #ifdef QTRAPIDS_DEBUG
163         qDebug() << "MainWindow::addPluginWidget():" << dlView_->currentItem();
164 #endif
165
166         if (type == qtrapids::PluginHostInterface::TAB_PAGE) {
167                 int index = tabWidget_->addTab(widget, tr("Results"));
168                 tabWidget_->setCurrentIndex(index);
169                 //layout_->addWidget(widget);
170         }
171 }
172 void MainWindow::addToolbar(QWidget* widget, PluginWidgetType type)
173 {
174 }
175
176 void MainWindow::addToolItem(QWidget* widget, PluginWidgetType type)
177 {
178 }
179
180 void MainWindow::addMenu(QWidget* widget, PluginWidgetType type)
181 {
182 }
183
184 void MainWindow::addMenuItem(QWidget* widget, PluginWidgetType type)
185 {
186 }
187
188 bool MainWindow::eventRequest(QVariant param, PluginRequest req)
189 {
190         if (req == qtrapids::PluginHostInterface::OPEN_FILE) {
191                 QString sourceFile = param.toString();
192                 
193                 // Get the source files name from the full path:
194                 QFileInfo fInfo(sourceFile);
195                 QString targetFile = fInfo.fileName();
196                 targetFile = settings_.value("download/directory").toString() + "/" + targetFile;
197                 
198                 // Copy temoporary file to Downloads directory...
199                 if (!QFile::copy(sourceFile, targetFile)) {
200                         qDebug() << "File copying failed";
201                         return false;
202                 } else {
203                         // If copying was successful, remove the original temporary file.
204                         QFile::remove(sourceFile);
205                 }
206                 
207                 /// @todo Torrent bencoding validity should be checked before starting(?)
208                 // ...and start the torrent:
209                 on_torrentFileSelected(targetFile);
210         
211         } else if (req == qtrapids::PluginHostInterface::READ_BUFFER) {
212                 // Create torrent information from char* buffer and start.
213                 StartTorrentFromBufferData(param.toByteArray().constData(), param.toByteArray().size());
214         }
215         
216         return true;
217 }
218
219
220 //=========================== PRIVATE ================================
221
222 void MainWindow::LoadPlugins()
223 {
224         // Get plugin directories from 
225         QStringList pluginDirsTmp = settings_.value("plugins/path").toStringList();
226         QStringList nameFilters("*.so");
227         
228         /// @todo enable "application directory" for plugin search in development/debug mode only. In release version
229         /// search plugins directory under $HOME/.qtrapids or system library paths
230         pluginDirsTmp << qApp->applicationDirPath();
231         pluginDirsTmp.removeDuplicates();
232         
233         foreach (QString dir, pluginDirsTmp) {
234                 pluginDirs_.append(QDir(dir));
235         }
236         
237         foreach (QDir dir, pluginDirs_) {
238                 
239                 if (dir.cd(PLUGINS_DIR)) {
240                         
241                         foreach (QString fileName, dir.entryList(nameFilters, QDir::Files)) {
242                                 QPluginLoader pluginLoader(dir.absoluteFilePath(fileName));
243
244                                 // If plugin not loaded from another directory, then load
245                                 if (!pluginFileNames_.contains(fileName) && QLibrary::isLibrary(fileName)) {
246
247                                         if (pluginLoader.load()) {
248                                                 qDebug() << "Plugin loaded: "  << fileName;
249                                         } else {
250                                                 qWarning() << "Plugin load failed: " << pluginLoader.errorString();
251                                         }
252
253                                         QObject *baseInstance = pluginLoader.instance();
254                                         if (!baseInstance) {
255                                                 qDebug() << "Base instance = NULL.";
256                                         }
257
258                                         qtrapids::PluginInterface *plugin = qobject_cast<qtrapids::PluginInterface*>(baseInstance);
259
260                                         if (!plugin) {
261                                                 qDebug() << "Cast failed.";
262                                         } else {
263                                                 qtrapids::PluginInterface::Info info;
264                                                 info.directory = dir.path();
265                                                 qDebug() << dir.path();
266                                                 plugin->initialize(this, info);
267                                                 pluginFileNames_ += fileName;
268                                         }
269                                 } else {
270                                         qWarning() << "Plugin " 
271                                                 << fileName 
272                                                 << " already loaded from another directory, or not a valid library file";
273                                 }
274                         }
275                         
276                 } else {
277                         qWarning() << PLUGINS_DIR <<  "directory not accessible or does not exist in "  << dir.path();
278                 }
279         }
280 }
281
282
283 void MainWindow::RestoreSettings()
284 {
285         
286         // Restore previous main window geometry:
287         QVariant geometry(settings_.value("geometry"));
288         if (!geometry.isNull()) {
289                 qDebug() << "restoring geometry";
290                 restoreGeometry(geometry.toByteArray());
291         }
292         
293         // Restore torrent session settings to server:
294         qtrapids::ParamsMap_t options;
295         options["net/downloadRate"] = settings_.value("net/downloadRate").toString();
296         options["net/uploadRate"] = settings_.value("net/uploadRate").toString();
297         server_.setOptions(options);
298 }
299
300
301 // Opens torrent information from buffer data and adds torrent to session 
302 void MainWindow::StartTorrentFromBufferData(char const* data, int size)
303 {
304
305 }
306
307 // =========================== PRIVATE SLOTS =================================
308 void MainWindow::on_openAction_clicked()
309 {
310         QFileDialog *dialog = new QFileDialog( this, "Open torrent file", QString(), tr("Torrent files (*.torrent)"));
311         dialog->setFileMode(QFileDialog::ExistingFile);
312         connect(dialog, SIGNAL(fileSelected(const QString&)), this, SLOT(on_torrentFileSelected(const QString&)));
313         dialog->show();
314
315 }
316
317 void MainWindow::on_removeAction_clicked()
318 {
319         QString hash = dlView_->prepareRemoveSelected();
320         try {
321                 server_.removeTorrent(hash);
322         } catch (...) {
323                 qDebug() << "Exception removing torrent";
324         }
325 }
326
327
328 void MainWindow::on_quitAction_clicked()
329 {
330         close();
331 }
332
333
334 void MainWindow::on_preferencesAction_clicked()
335 {
336         if (!preferencesDialog_) {
337                 preferencesDialog_ = new PreferencesDialog(this, 0, &server_);
338         }
339
340         preferencesDialog_->show();
341         preferencesDialog_->raise();
342         preferencesDialog_->activateWindow();
343 }
344
345
346 void MainWindow::on_aboutAction_clicked()
347 {
348         QMessageBox::about(this, tr("About QtRapids"), ABOUT_TEXT);
349 }
350
351
352 void MainWindow::on_aboutQtAction_clicked()
353 {
354         QMessageBox::aboutQt (this, tr("About Qt"));
355 }
356
357
358 void MainWindow::on_tabWidget_tabCloseRequested(int index)
359 {
360         
361         int searchWidgetIndex = tabWidget_->indexOf(searchWidget_);
362         
363         // Allow closing other tabs than the first two
364         // TODO The first two may well be closable, just add "show tabs" action for these in the menu
365         if (index != 0 && index != 1 && index != searchWidgetIndex) {
366                 QWidget *remove = tabWidget_->widget(index);
367                 tabWidget_->removeTab(index);
368                 delete remove;
369                 remove = NULL;
370         }
371 }
372
373
374 void MainWindow::on_downloadItemSelectionChanged()
375 {
376 #ifdef QTRAPIDS_DEBUG
377         qDebug() << "MainWindow::on_seedItemSelectionChanged():" << dlView_->currentItem();
378 #endif
379         if (dlView_->currentItem() != NULL) {
380                 emit(itemSelected(true));
381         } else {
382                 emit(itemSelected(false));
383         }
384 }
385
386
387 void MainWindow::on_seedItemSelectionChanged()
388 {
389 #ifdef QTRAPIDS_DEBUG
390         qDebug() << "MainWindow::on_seedItemSelectionChanged():" << seedView_->currentItem();
391 #endif
392         if (seedView_->currentItem() != NULL) {
393                 emit(itemSelected(true));
394         } else {
395                 emit(itemSelected(false));
396         }
397 }
398
399
400 void MainWindow::handleToolBarAction(QAction* action)
401 {
402         if (action->text() == "Open") {
403                 on_openAction_clicked();
404         } else if (action->text() == "Remove") {
405                 on_removeAction_clicked();
406         }
407 }
408
409
410 void MainWindow::on_torrentFileSelected(const QString& file)
411 {
412 #ifdef QTRAPIDS_DEBUG
413         qDebug() << " MainWindow::on_torrentFileSelected(): " << file;
414 #endif
415         // Torrent filename empty, do nothing.
416         if (file == "") {
417                 return;
418         }
419
420         // Otherwise add torrent
421         // For params, see: http://www.rasterbar.com/products/libtorrent/manual.html#add-torrent
422         // save_path is the only mandatory parameter, rest are optional.
423         //addParams.storage_mode = libtorrent::storage_mode_allocate;
424         try {
425                 server_.addTorrent(file, settings_.value("download/directory").toString()
426                                    , ParamsMap_t());
427         } catch (...) {
428                 qDebug() << "Exception adding torrent";
429         }
430 }
431
432
433 void MainWindow::on_alert(qtrapids::TorrentState info, qtrapids::ParamsMap_t other_info)
434 {
435 #ifdef QTRAPIDS_DEBUG
436         qDebug() << "got alert";
437 #endif
438         dlView_->updateItem(info, other_info);
439 }
440
441 } // namespace qtrapids