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
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"
23 #include <kio/netaccess.h>
24 #include <kinputdialog.h>
25 #include <kfiledialog.h>
26 #include <kmessagebox.h>
28 #include <kstandarddirs.h>
29 #include <kservicetypetrader.h>
30 #include <klibloader.h>
31 #include <kiconloader.h>
32 #include <kactioncollection.h>
35 #include <QTextStream>
36 #include <QDomElement>
37 #include <QApplication>
39 #include <QItemSelectionModel>
40 #include <QAbstractItemView>
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?).
52 KGet
& KGet::self( MainWindow
* mainWindow
)
56 m_mainWindow
= mainWindow
;
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
))
94 TransferGroup
* group
= new TransferGroup(m_transferTreeModel
, m_scheduler
, groupName
);
96 m_transferTreeModel
->addGroup(group
);
99 postAddedTransferGroupEvent(group
);
104 void KGet::delGroup(const QString
& groupName
)
106 TransferGroup
* group
= m_transferTreeModel
->findGroup(groupName
);
110 m_transferTreeModel
->delGroup(group
);
111 postRemovedTransferGroupEvent(group
);
116 void KGet::addMetaLink(const KUrl
&srcUrl
)
118 kDebug(5001) << " addMetaLink: " << srcUrl
.url() << endl
;
120 if ( !isValidSource( srcUrl
) )
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
;
136 if ( srcUrl
.isEmpty() )
138 //No src location: we let the user insert it manually
139 srcUrl
= urlInputDialog();
140 if( srcUrl
.isEmpty() )
144 if ( !isValidSource( srcUrl
) )
147 if (!isValidDestDirectory(destDir
) && !Settings::useDefaultDirectory())
148 destDir
= destInputDialog();
150 if (Settings::useDefaultDirectory())
151 destDir
= getSaveDirectoryFromDefault(srcUrl
);
153 if( (destUrl
= getValidDestUrl( destDir
, srcUrl
)).isEmpty() )
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
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()) )
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 )
194 if ( urlsToDownload
.count() == 1 )
196 // just one file -> ask for filename
197 addTransfer(srcUrls
.first(), destDir
, groupName
);
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
))
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.
238 void KGet::moveTransfer(TransferHandler
* transfer
, const QString
& 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");
300 //Try to save the transferlist to a temporary location
301 if(!KIO::NetAccess::download(KUrl(filename
), tmpFile
, 0))
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
;
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());
333 postAddedTransferGroupEvent(newGroup
);
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());
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
) )
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");
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
),
388 QTextStream
stream( &file
);
389 doc
.save( stream
, 0 );
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
)
406 m_scheduler
->start();
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();
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 ------
442 m_transferTreeModel
= new TransferTreeModel(m_scheduler
);
443 m_selectionModel
= new TransferTreeSelectionModel(m_transferTreeModel
);
445 //Load all the available plugins
448 //Create the default group
449 addGroup(i18n("Default Group"));
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
);
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
);
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
;
493 observer
->addedTransferGroupEvent(group
->handler());
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
)
512 observer
->removedTransferGroupEvent(group
->handler());
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()
530 KUrl clipboardUrl
= KUrl(QApplication::clipboard()->text(QClipboard::Clipboard
).trimmed());
531 if (clipboardUrl
.isValid())
532 newtransfer
= clipboardUrl
.url();
536 newtransfer
= KInputDialog::getText(i18n("New Download"), i18n("Enter URL:"), newtransfer
, &ok
, 0);
540 //user pressed cancel
544 KUrl src
= KUrl(newtransfer
);
553 QString
KGet::destInputDialog()
555 QString destDir
= KFileDialog::getExistingDirectory(Settings::lastDirectory());
557 Settings::setLastDirectory( 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();
571 // odd list items are regular expressions for extensions
577 if (!ext
.startsWith('*'))
581 rexp
.setPatternSyntax(QRegExp::Wildcard
);
583 if (rexp
.exactMatch(filename
.url())) {
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()),
601 // Check if a transfer with the same url already exists
602 Transfer
* transfer
= m_transferTreeModel
->findTransfer( source
);
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" )
614 //TODO reimplement this
615 //transfer->slotRemove();
622 //transfer is not finished. Give an error message.
623 KMessageBox::error(0,
624 i18n("Already saving URL\n%1", source
.prettyUrl()),
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()))
647 safeDeleteFile( destUrl
);
655 KIO::open_RenameDlg(i18n("File already exists"),
656 (*it).url(), destUrl.url(),
661 KUrl
KGet::getValidDestUrl(const QString
& destDir
, const KUrl
&srcUrl
)
663 if ( !isValidDestDirectory(destDir
) )
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
;
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
;
692 void KGet::loadPlugins()
694 // Add versioning constraint
696 str
= "[X-KDE-KGet-framework-version] == ";
697 str
+= QString::number( FrameworkVersion
);
699 str
+= "[X-KDE-KGet-rank] > 0";
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
)
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
;
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();
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() ) );
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() ) );
776 KGetPlugin
* (*create_plugin
)() = ( KGetPlugin
* (*)() ) lib
->resolveSymbol( "create_plugin" );
778 if ( !create_plugin
)
780 kDebug(5001) << "create_plugin == NULL" << endl
;
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() );
796 KMessageBox::information(0L,i18n("Not deleting\n%1\nas it is a "
797 "directory.", url
.prettyUrl()),
798 i18n("Not Deleted"));
801 KIO::NetAccess::del( url
, 0L );
806 KMessageBox::information( 0L,
807 i18n("Not deleting\n%1\nas it is not a local"
808 " file.", url
.prettyUrl()),
809 i18n("Not Deleted") );