add DQT3_SUPPORT
[kdenetwork.git] / kget / core / kget.cpp
blobd3507f8879f6cb650b9681cd8cdc7f0cd0f5de6e
1 /* This file is part of the KDE project
3 Copyright (C) 2005 Dario Massarin <nekkar@libero.it>
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public
7 License as published by the Free Software Foundation; version 2
8 of the License.
9 */
11 #include "core/kget.h"
13 #include "mainwindow.h"
14 #include "core/transfer.h"
15 #include "core/transfergroup.h"
16 #include "core/transfertreemodel.h"
17 #include "core/transfertreeselectionmodel.h"
18 #include "core/plugin/plugin.h"
19 #include "core/plugin/transferfactory.h"
20 #include "core/observer.h"
21 #include "settings.h"
23 #include <kio/netaccess.h>
24 #include <kinputdialog.h>
25 #include <kfiledialog.h>
26 #include <kmessagebox.h>
27 #include <klocale.h>
28 #include <kstandarddirs.h>
29 #include <kservicetypetrader.h>
30 #include <klibloader.h>
31 #include <kiconloader.h>
32 #include <kactioncollection.h>
34 #include <QDirModel>
35 #include <QTextStream>
36 #include <QDomElement>
37 #include <QApplication>
38 #include <QClipboard>
39 #include <QItemSelectionModel>
40 #include <QAbstractItemView>
42 /**
43 * This is our KGet class. This is where the user's transfers and searches are
44 * stored and organized.
45 * Use this class from the views to add or remove transfers or searches
46 * In order to organize the transfers inside categories we have a TransferGroup
47 * class. By definition, a transfer must always belong to a TransferGroup. If we
48 * don't want it to be displayed by the gui inside a specific group, we will put
49 * it in the group named "Not grouped" (better name?).
50 **/
52 KGet& KGet::self( MainWindow * mainWindow )
54 if(mainWindow)
56 m_mainWindow = mainWindow;
59 static KGet m;
60 return m;
63 void KGet::addObserver(ModelObserver * observer)
65 kDebug(5001) << "KGet::addObserver" << endl;
67 m_observers.append(observer);
69 //Update the new observer with the TransferGroups objects of the model
70 QList<TransferGroup *>::const_iterator it = m_transferTreeModel->transferGroups().begin();
71 QList<TransferGroup *>::const_iterator itEnd = m_transferTreeModel->transferGroups().end();
73 for( ; it!=itEnd ; ++it )
75 postAddedTransferGroupEvent(*it, observer);
78 kDebug(5001) << "KGet::addObserver >>> EXITING" << endl;
81 void KGet::delObserver(ModelObserver * observer)
83 m_observers.removeAll(observer);
86 bool KGet::addGroup(const QString& groupName)
88 kDebug(5001) << "KGet::addGroup" << endl;
90 // Check if a group with that name already exists
91 if(m_transferTreeModel->findGroup(groupName))
92 return false;
94 TransferGroup * group = new TransferGroup(m_transferTreeModel, m_scheduler, groupName);
96 m_transferTreeModel->addGroup(group);
98 //post notifications
99 postAddedTransferGroupEvent(group);
101 return true;
104 void KGet::delGroup(const QString& groupName)
106 TransferGroup * group = m_transferTreeModel->findGroup(groupName);
108 if(group)
110 m_transferTreeModel->delGroup(group);
111 postRemovedTransferGroupEvent(group);
112 delete(group);
116 void KGet::addMetaLink(const KUrl &srcUrl)
118 kDebug(5001) << " addMetaLink: " << srcUrl.url() << endl;
120 if ( !isValidSource( srcUrl ) )
121 return;
123 QString destDir = destInputDialog();
124 KUrl destUrl(destDir);
126 createTransfer(srcUrl, destUrl, QString("MetaLinks"));
129 void KGet::addTransfer( KUrl srcUrl, QString destDir, // krazy:exclude=passbyvalue
130 const QString& groupName )
132 kDebug(5001) << " addTransfer: " << srcUrl.url() << endl;
134 KUrl destUrl;
136 if ( srcUrl.isEmpty() )
138 //No src location: we let the user insert it manually
139 srcUrl = urlInputDialog();
140 if( srcUrl.isEmpty() )
141 return;
144 if ( !isValidSource( srcUrl ) )
145 return;
147 if (!isValidDestDirectory(destDir) && !Settings::useDefaultDirectory())
148 destDir = destInputDialog();
150 if (Settings::useDefaultDirectory())
151 destDir = getSaveDirectoryFromDefault(srcUrl);
153 if( (destUrl = getValidDestUrl( destDir, srcUrl )).isEmpty() )
154 return;
156 createTransfer(srcUrl, destUrl, groupName);
159 void KGet::addTransfer(const QDomElement& e, const QString& groupName)
161 //We need to read these attributes now in order to know which transfer
162 //plugin to use.
163 KUrl srcUrl = KUrl( e.attribute("Source") );
164 KUrl destUrl = KUrl( e.attribute("Dest") );
166 kDebug(5001) << "KGet::addTransfer src= " << srcUrl.url()
167 << " dest= " << destUrl.url()
168 << " group= "<< groupName << endl;
170 if ( srcUrl.isEmpty() || !isValidSource(srcUrl)
171 || !isValidDestDirectory(destUrl.directory()) )
172 return;
174 createTransfer(srcUrl, destUrl, groupName, &e);
177 void KGet::addTransfer(KUrl::List srcUrls, QString destDir, // krazy:exclude=passbyvalue
178 const QString& groupName)
180 KUrl::List urlsToDownload;
182 KUrl::List::ConstIterator it = srcUrls.begin();
183 KUrl::List::ConstIterator itEnd = srcUrls.end();
185 for(; it!=itEnd ; ++it)
187 if ( isValidSource( *it ) )
188 urlsToDownload.append( *it );
191 if ( urlsToDownload.count() == 0 )
192 return;
194 if ( urlsToDownload.count() == 1 )
196 // just one file -> ask for filename
197 addTransfer(srcUrls.first(), destDir, groupName);
198 return;
201 KUrl destUrl;
203 // multiple files -> ask for directory, not for every single filename
204 if (!isValidDestDirectory(destDir) && !Settings::useDefaultDirectory())
205 destDir = destInputDialog();
207 it = urlsToDownload.begin();
208 itEnd = urlsToDownload.end();
210 for ( ; it != itEnd; ++it )
212 destUrl = getValidDestUrl(destDir.isEmpty() ? getSaveDirectoryFromDefault(*it) : destDir, *it);
214 if(!isValidDestUrl(destUrl))
215 continue;
217 createTransfer(*it, destUrl, groupName);
222 void KGet::delTransfer(TransferHandler * transfer)
224 Transfer * t = transfer->m_transfer;
226 m_transferTreeModel->delTransfer(t);
228 //Here I delete the Transfer. The other possibility is to move it to a list
229 //and to delete all these transfers when kget gets closed. Obviously, after
230 //the notification to the views that the transfer has been removed, all the
231 //pointers to it are invalid.
232 transfer->postDeleteEvent();
233 // TODO: why does it crash if a download is going to be deleted which is not the last in the list?
234 // there are always no problems with the last download.
235 // delete( t );
238 void KGet::moveTransfer(TransferHandler * transfer, const QString& groupName)
240 Q_UNUSED(transfer);
241 Q_UNUSED(groupName);
244 QList<TransferHandler *> KGet::selectedTransfers()
246 // kDebug(5001) << "KGet::selectedTransfers" << endl;
248 QList<TransferHandler *> selectedTransfers;
250 QModelIndexList selectedIndexes = m_selectionModel->selectedRows();
252 foreach(QModelIndex currentIndex, selectedIndexes)
254 if(!m_transferTreeModel->isTransferGroup(currentIndex))
255 selectedTransfers.append(static_cast<TransferHandler *> (currentIndex.internalPointer()));
258 return selectedTransfers;
261 // This is the code that was used in the old selectedTransfers function
262 /* QList<TransferGroup *>::const_iterator it = m_transferTreeModel->transferGroups().begin();
263 QList<TransferGroup *>::const_iterator itEnd = m_transferTreeModel->transferGroups().end();
265 for( ; it!=itEnd ; ++it )
267 TransferGroup::iterator it2 = (*it)->begin();
268 TransferGroup::iterator it2End = (*it)->end();
270 for( ; it2!=it2End ; ++it2 )
272 Transfer * transfer = (Transfer*) *it2;
274 if( transfer->isSelected() )
275 selectedTransfers.append( transfer->handler() );
278 return selectedTransfers;*/
281 TransferTreeSelectionModel * KGet::selectionModel()
283 return m_selectionModel;
286 void KGet::addTransferView(QAbstractItemView * view)
288 view->setModel(m_transferTreeModel);
291 void KGet::load( QString filename ) // krazy:exclude=passbyvalue
293 kDebug(5001) << "KGet::load(" << filename << ")" << endl;
295 if(filename.isEmpty())
296 filename = KStandardDirs::locateLocal("appdata", "transfers.kgt");
298 QString tmpFile;
300 //Try to save the transferlist to a temporary location
301 if(!KIO::NetAccess::download(KUrl(filename), tmpFile, 0))
302 return;
304 QFile file(tmpFile);
305 QDomDocument doc;
307 kDebug(5001) << "KGet::load file" << filename << endl;
309 if(doc.setContent(&file))
311 QDomElement root = doc.documentElement();
313 QDomNodeList nodeList = root.elementsByTagName("TransferGroup");
314 int nItems = nodeList.length();
316 for( int i = 0 ; i < nItems ; i++ )
318 TransferGroup * foundGroup = m_transferTreeModel->findGroup( nodeList.item(i).toElement().attribute("Name") );
320 kDebug(5001) << "KGet::load -> group = " << nodeList.item(i).toElement().attribute("Name") << endl;
322 if( !foundGroup )
324 kDebug(5001) << "KGet::load -> group not found" << endl;
326 TransferGroup * newGroup = new TransferGroup(m_transferTreeModel, m_scheduler);
328 m_transferTreeModel->addGroup(newGroup);
330 newGroup->load(nodeList.item(i).toElement());
332 //Post notifications
333 postAddedTransferGroupEvent(newGroup);
335 else
337 kDebug(5001) << "KGet::load -> group found" << endl;
339 //A group with this name already exists.
340 //Integrate the group's transfers with the ones read from file
341 foundGroup->load(nodeList.item(i).toElement());
345 else
347 kWarning(5001) << "Error reading the transfers file" << endl;
351 void KGet::save( QString filename ) // krazy:exclude=passbyvalue
353 if ( !filename.isEmpty()
354 && QFile::exists( filename )
355 && (KMessageBox::questionYesNoCancel(0,
356 i18n("The file %1 already exists.\nOverwrite?", filename),
357 i18n("Overwrite existing file?"), KStandardGuiItem::yes(),
358 KStandardGuiItem::no(), KStandardGuiItem::cancel(), "QuestionFilenameExists" )
359 != KMessageBox::Yes) )
360 return;
362 if(filename.isEmpty())
363 filename = KStandardDirs::locateLocal("appdata", "transfers.kgt");
365 QDomDocument doc(QString("KGetTransfers"));
366 QDomElement root = doc.createElement("Transfers");
367 doc.appendChild(root);
369 QList<TransferGroup *>::const_iterator it = m_transferTreeModel->transferGroups().begin();
370 QList<TransferGroup *>::const_iterator itEnd = m_transferTreeModel->transferGroups().end();
372 for ( ; it!=itEnd ; ++it )
374 QDomElement e = doc.createElement("TransferGroup");
375 root.appendChild(e);
376 (*it)->save(e);
378 QFile file(filename);
379 if ( !file.open( QIODevice::WriteOnly ) )
381 //kWarning(5001)<<"Unable to open output file when saving"<< endl;
382 KMessageBox::error(0,
383 i18n("Unable to save to: %1", filename),
384 i18n("Error"));
385 return;
388 QTextStream stream( &file );
389 doc.save( stream, 0 );
390 file.close();
393 TransferFactory * KGet::factory(TransferHandler * transfer)
395 return transfer->m_transfer->factory();
398 KActionCollection * KGet::actionCollection()
400 return m_mainWindow->actionCollection();
403 void KGet::setSchedulerRunning(bool running)
405 if(running)
406 m_scheduler->start();
407 else
408 m_scheduler->stop();
411 bool KGet::schedulerRunning()
413 return (m_scheduler->countRunningJobs() > 0);
416 void KGet::setPluginsSettingsWidget(KTabWidget * widget)
418 QList<TransferFactory *>::iterator it = m_transferFactories.begin();
419 QList<TransferFactory *>::iterator itEnd = m_transferFactories.end();
421 QWidget * settingsWidget;
422 for( ; it!=itEnd ; ++it)
424 settingsWidget = (*it)->createSettingsWidget();
425 if(settingsWidget)
426 widget->addTab( settingsWidget, (*it)->displayName() );
430 // ------ STATIC MEMBERS INITIALIZATION ------
431 QList<ModelObserver *> KGet::m_observers;
432 TransferTreeModel * KGet::m_transferTreeModel;
433 TransferTreeSelectionModel * KGet::m_selectionModel;
434 QList<TransferFactory *> KGet::m_transferFactories;
435 QList<KLibrary *> KGet::m_pluginKLibraries;
436 Scheduler * KGet::m_scheduler = new Scheduler();
437 MainWindow * KGet::m_mainWindow = 0;
439 // ------ PRIVATE FUNCTIONS ------
440 KGet::KGet()
442 m_transferTreeModel = new TransferTreeModel(m_scheduler);
443 m_selectionModel = new TransferTreeSelectionModel(m_transferTreeModel);
445 //Load all the available plugins
446 loadPlugins();
448 //Create the default group
449 addGroup(i18n("Default Group"));
452 KGet::~KGet()
454 unloadPlugins();
455 delete(m_scheduler);
458 void KGet::createTransfer(const KUrl &src, const KUrl &dest, const QString& groupName, const QDomElement * e)
460 kDebug(5001) << "createTransfer: srcUrl= " << src.url() << " "
461 << "destUrl= " << dest.url()
462 << "group= _" << groupName << "_" << endl;
464 TransferGroup * group = m_transferTreeModel->findGroup(groupName);
465 if (group==0)
467 kDebug(5001) << "KGet::createTransfer -> group not found" << endl;
468 group = m_transferTreeModel->transferGroups().first();
470 Transfer * newTransfer;
472 QList<TransferFactory *>::iterator it = m_transferFactories.begin();
473 QList<TransferFactory *>::iterator itEnd = m_transferFactories.end();
475 for( ; it!=itEnd ; ++it)
477 kDebug(5001) << "Trying plugin n.plugins=" << m_transferFactories.size() << endl;
478 if((newTransfer = (*it)->createTransfer(src, dest, group, m_scheduler, e)))
480 // kDebug(5001) << "KGet::createTransfer -> CREATING NEW TRANSFER ON GROUP: _" << group->name() << "_" << endl;
481 m_transferTreeModel->addTransfer(newTransfer, group);
482 return;
485 kDebug(5001) << "createTransfer: Warning! No plugin found to handle the given url" << endl;
488 void KGet::postAddedTransferGroupEvent(TransferGroup * group, ModelObserver * observer)
490 kDebug(5001) << "KGet::postAddedTransferGroupEvent" << endl;
491 if(observer)
493 observer->addedTransferGroupEvent(group->handler());
494 return;
497 QList<ModelObserver *>::iterator it = m_observers.begin();
498 QList<ModelObserver *>::iterator itEnd = m_observers.end();
500 for(; it!=itEnd; ++it)
502 kDebug(5001) << "message posted" << endl;
504 (*it)->addedTransferGroupEvent(group->handler());
508 void KGet::postRemovedTransferGroupEvent(TransferGroup * group, ModelObserver * observer)
510 if(observer)
512 observer->removedTransferGroupEvent(group->handler());
513 return;
516 QList<ModelObserver *>::iterator it = m_observers.begin();
517 QList<ModelObserver *>::iterator itEnd = m_observers.end();
519 for(; it!=itEnd; ++it)
521 (*it)->removedTransferGroupEvent(group->handler());
525 KUrl KGet::urlInputDialog()
527 QString newtransfer;
528 bool ok = false;
530 KUrl clipboardUrl = KUrl(QApplication::clipboard()->text(QClipboard::Clipboard).trimmed());
531 if (clipboardUrl.isValid())
532 newtransfer = clipboardUrl.url();
534 while (!ok)
536 newtransfer = KInputDialog::getText(i18n("New Download"), i18n("Enter URL:"), newtransfer, &ok, 0);
538 if (!ok)
540 //user pressed cancel
541 return KUrl();
544 KUrl src = KUrl(newtransfer);
545 if(src.isValid())
546 return src;
547 else
548 ok = false;
550 return KUrl();
553 QString KGet::destInputDialog()
555 QString destDir = KFileDialog::getExistingDirectory(Settings::lastDirectory());
557 Settings::setLastDirectory( destDir );
558 return destDir;
561 QString KGet::getSaveDirectoryFromDefault(const KUrl &filename)
563 // use global default folder as default.
564 // if extension is matching with one in the list, overwrite it below.
565 QString destDir = Settings::defaultDirectory();
567 QStringList list = Settings::extensionsFolderList();
568 QStringList::Iterator it = list.begin();
569 QStringList::Iterator end = list.end();
570 while (it != end) {
571 // odd list items are regular expressions for extensions
572 QString ext = *it;
573 ++it;
574 QString path = *it;
575 ++it;
577 if (!ext.startsWith('*'))
578 ext = '*' + ext;
580 QRegExp rexp(ext);
581 rexp.setPatternSyntax(QRegExp::Wildcard);
583 if (rexp.exactMatch(filename.url())) {
584 destDir = path;
585 break;
589 return destDir.replace("file://", "");
592 bool KGet::isValidSource(KUrl source)
594 if (!source.isValid())
596 KMessageBox::error(0,
597 i18n("Malformed URL:\n%1", source.prettyUrl()),
598 i18n("Error"));
599 return false;
601 // Check if a transfer with the same url already exists
602 Transfer * transfer = m_transferTreeModel->findTransfer( source );
603 if ( transfer )
605 if ( transfer->status() == Job::Finished )
607 // transfer is finished, ask if we want to download again
608 if (KMessageBox::questionYesNoCancel(0,
609 i18n("URL already saved:\n%1\nDownload again?", source.prettyUrl()),
610 i18n("Download URL again?"), KStandardGuiItem::yes(),
611 KStandardGuiItem::no(), KStandardGuiItem::cancel(), "QuestionUrlAlreadySaved" )
612 == KMessageBox::Yes)
614 //TODO reimplement this
615 //transfer->slotRemove();
616 //checkQueue();
617 return true;
620 else
622 //transfer is not finished. Give an error message.
623 KMessageBox::error(0,
624 i18n("Already saving URL\n%1", source.prettyUrl()),
625 i18n("Error"));
626 return false;
628 return false;
630 return true;
633 bool KGet::isValidDestDirectory(const QString & destDir)
635 return (!destDir.isEmpty() && QFileInfo( destDir ).isDir());
638 bool KGet::isValidDestUrl(KUrl destUrl)
640 if(KIO::NetAccess::exists(destUrl, false, 0))
642 if (KMessageBox::warningYesNoCancel(0,
643 i18n("Destination file \n%1\nalready exists.\n"
644 "Do you want to overwrite it?", destUrl.prettyUrl()))
645 == KMessageBox::Yes)
647 safeDeleteFile( destUrl );
648 return true;
650 else
651 return false;
653 return true;
655 KIO::open_RenameDlg(i18n("File already exists"),
656 (*it).url(), destUrl.url(),
657 KIO::M_MULTI);
661 KUrl KGet::getValidDestUrl(const QString& destDir, const KUrl &srcUrl)
663 if ( !isValidDestDirectory(destDir) )
664 return KUrl();
666 // create a proper destination file from destDir
667 KUrl destUrl = KUrl( destDir );
668 QString filename = srcUrl.fileName();
670 if ( filename.isEmpty() )
672 // simply use the full url as filename
673 filename = QUrl::toPercentEncoding( srcUrl.prettyUrl(), "/" );
674 kDebug(5001) << " Filename is empty. Setting to " << filename << endl;
675 kDebug(5001) << " srcUrl = " << srcUrl.url() << endl;
676 kDebug(5001) << " prettyUrl = " << srcUrl.prettyUrl() << endl;
678 else
680 kDebug(5001) << " Filename is not empty" << endl;
681 destUrl.adjustPath( KUrl::AddTrailingSlash );
682 destUrl.setFileName( filename );
683 if (!isValidDestUrl(destUrl))
685 kDebug(5001) << " destUrl " << destUrl.path() << " is not valid" << endl;
686 return KUrl();
689 return destUrl;
692 void KGet::loadPlugins()
694 // Add versioning constraint
695 QString
696 str = "[X-KDE-KGet-framework-version] == ";
697 str += QString::number( FrameworkVersion );
698 str += " and ";
699 str += "[X-KDE-KGet-rank] > 0";
700 str += " and ";
701 str += "[X-KDE-KGet-plugintype] == ";
703 KService::List offers;
705 //TransferFactory plugins
706 offers = KServiceTypeTrader::self()->query( "KGet/Plugin", str + "'TransferFactory'" );
708 //Here we use a QMap only to easily sort the plugins by rank
709 QMap<int, KService::Ptr> services;
710 QMap<int, KService::Ptr>::iterator it;
712 for ( int i = 0; i < offers.count(); ++i )
714 services[ offers[i]->property( "X-KDE-KGet-rank" ).toInt() ] = offers[i];
715 kDebug(5001) << " TransferFactory plugin found:" << endl <<
716 " rank = " << offers[i]->property( "X-KDE-KGet-rank" ).toInt() << endl <<
717 " plugintype = " << offers[i]->property( "X-KDE-KGet-plugintype" ) << endl;
720 //I must fill this pluginList before and my m_transferFactories list after.
721 //This because calling the KLibLoader::globalLibrary() erases the static
722 //members of this class (why?), such as the m_transferFactories list.
723 QList<KGetPlugin *> pluginList;
725 for( it = services.begin(); it != services.end(); ++it )
727 KGetPlugin * plugin;
728 if( (plugin = createPluginFromService(*it)) != 0 )
730 pluginList.prepend(plugin);
731 kDebug(5001) << "TransferFactory plugin (" << (*it)->library()
732 << ") found and added to the list of available plugins" << endl;
734 else
735 kDebug(5001) << "Error loading TransferFactory plugin ("
736 << (*it)->library() << ")" << endl;
739 QList<KGetPlugin *>::iterator it2 = pluginList.begin();
740 QList<KGetPlugin *>::iterator it2End = pluginList.end();
742 for( ; it2!=it2End ; ++it2 )
743 m_transferFactories.append( static_cast<TransferFactory *>(*it2) );
745 kDebug(5001) << "Number of factories = " << m_transferFactories.size() << endl;
748 void KGet::unloadPlugins()
750 QList<KLibrary *>::iterator it = m_pluginKLibraries.begin();
751 QList<KLibrary *>::iterator itEnd = m_pluginKLibraries.end();
753 for(;it!=itEnd;++it)
755 (*it)->unload();
757 m_transferFactories.clear();
760 KGetPlugin * KGet::createPluginFromService( const KService::Ptr service )
762 //get the library loader instance
763 KLibLoader *loader = KLibLoader::self();
765 //try to load the specified library
766 //Warning! This line seems to erase my m_transferFactories list!!
767 KLibrary *lib = loader->library( QFile::encodeName( service->library() ) );
769 if ( !lib )
771 KMessageBox::error( 0, i18n( "<p>KLibLoader could not load the plugin:<br/><i>%1</i></p>"
772 "<p>Error message:<br/><i>%2</i></p>", service->library(), loader->lastErrorMessage() ) );
773 return 0;
776 KGetPlugin* (*create_plugin)() = ( KGetPlugin* (*)() ) lib->resolveSymbol( "create_plugin" );
778 if ( !create_plugin )
780 kDebug(5001) << "create_plugin == NULL" << endl;
781 return 0;
784 m_pluginKLibraries.append(lib);
786 return create_plugin();
789 bool KGet::safeDeleteFile( const KUrl& url )
791 if ( url.isLocalFile() )
793 QFileInfo info( url.path() );
794 if ( info.isDir() )
796 KMessageBox::information(0L,i18n("Not deleting\n%1\nas it is a "
797 "directory.", url.prettyUrl()),
798 i18n("Not Deleted"));
799 return false;
801 KIO::NetAccess::del( url, 0L );
802 return true;
805 else
806 KMessageBox::information( 0L,
807 i18n("Not deleting\n%1\nas it is not a local"
808 " file.", url.prettyUrl()),
809 i18n("Not Deleted") );
810 return false;