diff options
Diffstat (limited to 'src/newstuff')
-rw-r--r-- | src/newstuff/Makefile.am | 18 | ||||
-rw-r--r-- | src/newstuff/dialog.cpp | 477 | ||||
-rw-r--r-- | src/newstuff/dialog.h | 101 | ||||
-rw-r--r-- | src/newstuff/manager.cpp | 446 | ||||
-rw-r--r-- | src/newstuff/manager.h | 101 | ||||
-rw-r--r-- | src/newstuff/newscript.cpp | 48 | ||||
-rw-r--r-- | src/newstuff/newscript.h | 60 | ||||
-rw-r--r-- | src/newstuff/providerloader.cpp | 102 | ||||
-rw-r--r-- | src/newstuff/providerloader.h | 84 |
9 files changed, 1437 insertions, 0 deletions
diff --git a/src/newstuff/Makefile.am b/src/newstuff/Makefile.am new file mode 100644 index 0000000..9054dcb --- /dev/null +++ b/src/newstuff/Makefile.am @@ -0,0 +1,18 @@ +noinst_LIBRARIES = libnewstuff.a + +libnewstuff_a_METASOURCES = AUTO + +libnewstuff_a_SOURCES = manager.cpp dialog.cpp newscript.cpp providerloader.cpp + +CLEANFILES = *~ + +EXTRA_DIST = \ +manager.h manager.cpp \ +dialog.h dialog.cpp \ +newscript.h newscript.cpp \ +providerloader.h providerloader.cpp + +AM_CPPFLAGS = $(all_includes) + +KDE_OPTIONS = noautodist + diff --git a/src/newstuff/dialog.cpp b/src/newstuff/dialog.cpp new file mode 100644 index 0000000..c13a249 --- /dev/null +++ b/src/newstuff/dialog.cpp @@ -0,0 +1,477 @@ +/*************************************************************************** + copyright : (C) 2006 by Robby Stephenson + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of version 2 of the GNU General Public License as * + * published by the Free Software Foundation; * + * * + ***************************************************************************/ + +#include "dialog.h" +#include "providerloader.h" +#include "../gui/listview.h" +#include "../latin1literal.h" +#include "../tellico_utils.h" +#include "../tellico_debug.h" + +#include <klocale.h> +#include <kpushbutton.h> +#include <kconfig.h> +#include <kiconloader.h> +#include <kstatusbar.h> +#include <kio/job.h> +#include <kio/netaccess.h> +#include <kaccelmanager.h> +#include <knewstuff/entry.h> +#include <knewstuff/provider.h> +#include <ktempfile.h> + +#include <qlabel.h> +#include <qtextedit.h> +#include <qlayout.h> +#include <qwhatsthis.h> +#include <qregexp.h> +#include <qvbox.h> +#include <qimage.h> +#include <qtimer.h> +#include <qprogressbar.h> + +#if KDE_IS_VERSION(3,4,90) +#define ENTRYNAME(e) e->name(m_lang) +#define ENTRYSUMM(e) e->summary(m_lang) +#define ENTRYEMAIL(e) e->authorEmail() +#else +#define ENTRYNAME(e) e->name() +#define ENTRYSUMM(e) e->summary() +#define ENTRYEMAIL(e) QString() +#endif + +namespace { + static const int NEW_STUFF_MIN_WIDTH = 600; + static const int NEW_STUFF_MIN_HEIGHT = 400; + static const int PROGRESS_STATUS_ID = 0; +} + +using Tellico::NewStuff::Dialog; + +class Dialog::Item : public GUI::ListViewItem { +public: + Item(GUI::ListView* parent) : GUI::ListViewItem(parent) {} + + InstallStatus status() const { return m_status; } + void setStatus(InstallStatus status) { + m_status = status; + if(m_status == Current) { + setPixmap(0, SmallIcon(QString::fromLatin1("ok"))); + } else if(m_status == OldVersion) { + setPixmap(0, SmallIcon(QString::fromLatin1("reload"))); + } + } + + QString key(int col, bool asc) const { + if(col == 2 || col == 3) { + QString s; + s.sprintf("%08d", text(col).toInt()); + return s; + } else if(col == 4) { + QString s; + QDate date = KGlobal::locale()->readDate(text(col)); + s.sprintf("%08d", date.year() * 366 + date.dayOfYear()); + return s; + } + return GUI::ListViewItem::key(col, asc); + } + +private: + InstallStatus m_status; +}; + +Dialog::Dialog(NewStuff::DataType type_, QWidget* parent_) + : KDialogBase(KDialogBase::Plain, i18n("Get Hot New Stuff"), 0, (KDialogBase::ButtonCode)0, parent_) + , m_manager(new Manager(this)) + , m_type(type_) + , m_timer(new QTimer(this)) + , m_cursorSaver(new GUI::CursorSaver()) + , m_tempPreviewImage(0) + , m_lastPreviewItem(0) { + + m_lang = KGlobal::locale()->language(); + + QFrame* frame = plainPage(); + QBoxLayout* boxLayout = new QVBoxLayout(frame, 0, KDialog::spacingHint()); + + m_split = new QSplitter(Qt::Vertical, frame); + boxLayout->addWidget(m_split); + + m_listView = new GUI::ListView(m_split); + m_listView->setAllColumnsShowFocus(true); + m_listView->setSelectionMode(QListView::Single); + m_listView->addColumn(i18n("Name")); + m_listView->addColumn(i18n("Version")); + m_listView->addColumn(i18n("Rating")); + m_listView->addColumn(i18n("Downloads")); + m_listView->addColumn(i18n("Release Date")); + m_listView->setSorting(2, false); + m_listView->setResizeMode(QListView::AllColumns); + connect(m_listView, SIGNAL(clicked(QListViewItem*)), SLOT(slotSelected(QListViewItem*))); + QWhatsThis::add(m_listView, i18n("This is a list of all the items available for download. " + "Previously installed items have a checkmark icon, while " + "items with new version available have an update icon")); + + QWidget* widget = new QWidget(m_split); + QBoxLayout* boxLayout2 = new QVBoxLayout(widget, 0, KDialog::spacingHint()); + + m_iconLabel = new QLabel(widget); + m_iconLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); + m_iconLabel->setMargin(0); + + m_nameLabel = new QLabel(widget); + QFont font = m_nameLabel->font(); + font.setBold(true); + font.setItalic(true); + m_nameLabel->setFont(font); + QWhatsThis::add(m_nameLabel, i18n("The name and license of the selected item")); + + m_infoLabel = new QLabel(widget); + QWhatsThis::add(m_infoLabel, i18n("The author of the selected item")); + + m_install = new KPushButton(i18n("Install"), widget); + m_install->setIconSet(SmallIconSet(QString::fromLatin1("knewstuff"))); + m_install->setEnabled(false); + connect(m_install, SIGNAL(clicked()), SLOT(slotInstall())); + + // the button's text changes later + // I don't want it resizing, so figure out the maximum size and set that + m_install->polish(); + int maxWidth = m_install->sizeHint().width(); + int maxHeight = m_install->sizeHint().height(); + m_install->setGuiItem(KGuiItem(i18n("Update"), SmallIconSet(QString::fromLatin1("knewstuff")))); + maxWidth = QMAX(maxWidth, m_install->sizeHint().width()); + maxHeight = QMAX(maxHeight, m_install->sizeHint().height()); + m_install->setMinimumWidth(maxWidth); + m_install->setMinimumHeight(maxHeight); + + QPixmap pix; + if(m_type == EntryTemplate) { + pix = DesktopIcon(QString::fromLatin1("looknfeel"), KIcon::SizeLarge); + QWhatsThis::add(m_install, i18n("Download and install the selected template.")); + } else { + pix = UserIcon(QString::fromLatin1("script")); + QWhatsThis::add(m_install, i18n("Download and install the selected script. Some scripts " + "may need to be configured after being installed.")); + } + m_iconLabel->setPixmap(pix); + + QBoxLayout* boxLayout3 = new QHBoxLayout(boxLayout2); + + QBoxLayout* boxLayout4 = new QVBoxLayout(boxLayout3); + boxLayout4->addWidget(m_iconLabel); + boxLayout4->addStretch(10); + + boxLayout3->addSpacing(4); + + QBoxLayout* boxLayout5 = new QVBoxLayout(boxLayout3); + boxLayout5->addWidget(m_nameLabel); + boxLayout5->addWidget(m_infoLabel); + boxLayout5->addStretch(10); + + boxLayout3->addStretch(10); + + QBoxLayout* boxLayout6 = new QVBoxLayout(boxLayout3); + boxLayout6->addWidget(m_install); + boxLayout6->addStretch(10); + + m_descLabel = new QTextEdit(widget); + m_descLabel->setReadOnly(true); + m_descLabel->setTextFormat(Qt::RichText); + m_descLabel->setPaper(colorGroup().background()); + m_descLabel->setMinimumHeight(5 * fontMetrics().height()); + boxLayout2->addWidget(m_descLabel, 10); + QWhatsThis::add(m_descLabel, i18n("A description of the selected item is shown here.")); + + QHBox* box = new QHBox(frame, "statusbox"); + boxLayout->addWidget(box); + box->setSpacing(KDialog::spacingHint()); + + m_statusBar = new KStatusBar(box, "statusbar"); + m_statusBar->insertItem(QString::null, PROGRESS_STATUS_ID, 1, false); + m_statusBar->setItemAlignment(PROGRESS_STATUS_ID, AlignLeft | AlignVCenter); + m_progress = new QProgressBar(m_statusBar, "progress"); + m_progress->setTotalSteps(0); + m_progress->setFixedHeight(fontMetrics().height()+2); + m_statusBar->addWidget(m_progress, 0, true); + + KPushButton* closeButton = new KPushButton(KStdGuiItem::close(), box); + connect(closeButton, SIGNAL(clicked()), SLOT(slotClose())); + closeButton->setFocus(); + + connect(m_timer, SIGNAL(timeout()), SLOT(slotMoveProgress())); + + setMinimumWidth(QMAX(minimumWidth(), NEW_STUFF_MIN_WIDTH)); + setMinimumHeight(QMAX(minimumHeight(), NEW_STUFF_MIN_HEIGHT)); + resize(configDialogSize(QString::fromLatin1("NewStuff Dialog Options"))); + + KConfigGroup dialogConfig(KGlobal::config(), "NewStuff Dialog Options"); + QValueList<int> splitList = dialogConfig.readIntListEntry("Splitter Sizes"); + if(!splitList.empty()) { + m_split->setSizes(splitList); + } + + setStatus(i18n("Downloading information...")); + + ProviderLoader* loader = new Tellico::NewStuff::ProviderLoader(this); + connect(loader, SIGNAL(providersLoaded(QPtrList<KNS::Provider>*)), SLOT(slotProviders(QPtrList<KNS::Provider>*))); + connect(loader, SIGNAL(percent(KIO::Job*, unsigned long)), SLOT(slotShowPercent(KIO::Job*, unsigned long))); + connect(loader, SIGNAL(error()), SLOT(slotProviderError())); + + KConfigGroup config(KGlobal::config(), "KNewStuff"); + QString prov = config.readEntry("ProvidersUrl"); + if(prov.isEmpty()) { + if(m_type == EntryTemplate) { + prov = QString::fromLatin1("http://periapsis.org/tellico/newstuff/tellicotemplates-providers.php"); + QString alt = QString::fromLatin1("http://download.kde.org/khotnewstuff/tellicotemplates-providers.xml"); + loader->setAlternativeProvider(alt); + } else { + prov = QString::fromLatin1("http://periapsis.org/tellico/newstuff/tellicoscripts-providers.php"); + } + } + if(m_type == EntryTemplate) { + m_typeName = QString::fromLatin1("tellico/entry-template"); + } else { + m_typeName = QString::fromLatin1("tellico/data-source"); + } + loader->load(m_typeName, prov); + + KAcceleratorManager::manage(this); +} + +Dialog::~Dialog() { + delete m_cursorSaver; + m_cursorSaver = 0; + + saveDialogSize(QString::fromLatin1("NewStuff Dialog Options")); + KConfigGroup config(KGlobal::config(), "NewStuff Dialog Options"); + config.writeEntry("Splitter Sizes", m_split->sizes()); +} + +void Dialog::slotProviderError() { + if(m_listView->childCount() == 0) { + myDebug() << "NewStuff::Dialog::slotCheckError() - no available items" << endl; + setStatus(QString()); + + delete m_cursorSaver; + m_cursorSaver = 0; + } +} + +void Dialog::slotProviders(QPtrList<KNS::Provider>* list_) { + for(KNS::Provider* prov = list_->first(); prov; prov = list_->next()) { + KIO::TransferJob* job = KIO::get(prov->downloadUrl()); + m_jobs[job] = prov; + connect(job, SIGNAL(data(KIO::Job*, const QByteArray&)), + SLOT(slotData(KIO::Job*, const QByteArray&))); + connect(job, SIGNAL(result(KIO::Job*)), SLOT(slotResult(KIO::Job*))); + connect(job, SIGNAL(percent(KIO::Job*, unsigned long)), + SLOT(slotShowPercent(KIO::Job*, unsigned long))); + } +} + +void Dialog::slotData(KIO::Job* job_, const QByteArray& data_) { + QDataStream stream(m_data[job_], IO_WriteOnly | IO_Append); + stream.writeRawBytes(data_.data(), data_.size()); +} + +void Dialog::slotResult(KIO::Job* job_) { +// myDebug() << "NewStuff::Dialog::slotResult()" << endl; + QDomDocument dom; + if(!dom.setContent(m_data[job_])) { + KNS::Provider* prov = m_jobs[job_]; + KURL u = prov ? prov->downloadUrl() : KURL(); + myDebug() << "NewStuff::Dialog::slotResult() - can't load result: " << u.url() << endl; + m_jobs.remove(job_); + if(m_jobs.isEmpty()) { + setStatus(i18n("Ready.")); + delete m_cursorSaver; + m_cursorSaver = 0; + } + return; + } + + QDomElement knewstuff = dom.documentElement(); + + for(QDomNode pn = knewstuff.firstChild(); !pn.isNull(); pn = pn.nextSibling()) { + QDomElement stuff = pn.toElement(); + if(stuff.isNull()) { + continue; + } + + if(stuff.tagName() == Latin1Literal("stuff")) { + KNS::Entry* entry = new KNS::Entry(stuff); + if(!entry->type().isEmpty() && entry->type() != m_typeName) { + myLog() << "NewStuff::Dialog::slotResult() - type mismatch, skipping " << ENTRYNAME(entry) << endl; + continue; + } + + addEntry(entry); + } + } + m_jobs.remove(job_); + if(m_jobs.isEmpty()) { + setStatus(i18n("Ready.")); + delete m_cursorSaver; + m_cursorSaver = 0; + } +} + +void Dialog::addEntry(KNS::Entry* entry_) { + if(!entry_) { + return; + } + + Item* item = new Item(m_listView); + item->setText(0, ENTRYNAME(entry_)); + item->setText(1, entry_->version()); + item->setText(2, QString::number(entry_->rating())); + item->setText(3, QString::number(entry_->downloads())); + item->setText(4, KGlobal::locale()->formatDate(entry_->releaseDate(), true /*short format */)); + item->setStatus(NewStuff::Manager::installStatus(entry_)); + m_entryMap.insert(item, entry_); + + if(!m_listView->selectedItem()) { + m_listView->setSelected(item, true); + slotSelected(item); + } +} + +void Dialog::slotSelected(QListViewItem* item_) { + if(!item_) { + return; + } + + KNS::Entry* entry = m_entryMap[item_]; + if(!entry) { + return; + } + + KURL preview = entry->preview(m_lang); + if(!preview.isEmpty() && preview.isValid()) { + delete m_tempPreviewImage; + m_tempPreviewImage = new KTempFile(); + m_tempPreviewImage->setAutoDelete(true); + KURL dest; + dest.setPath(m_tempPreviewImage->name()); + KIO::FileCopyJob* job = KIO::file_copy(preview, dest, -1, true, false, false); + connect(job, SIGNAL(result(KIO::Job*)), SLOT(slotPreviewResult(KIO::Job*))); + connect(job, SIGNAL(percent(KIO::Job*, unsigned long)), + SLOT(slotShowPercent(KIO::Job*, unsigned long))); + m_lastPreviewItem = item_; + } + QPixmap pix = m_type == EntryTemplate + ? DesktopIcon(QString::fromLatin1("looknfeel"), KIcon::SizeLarge) + : UserIcon(QString::fromLatin1("script")); + m_iconLabel->setPixmap(pix); + + QString license = entry->license(); + if(!license.isEmpty()) { + license.prepend('(').append(')'); + } + QString name = QString::fromLatin1("%1 %2").arg(ENTRYNAME(entry)).arg(license); + QFont font = m_nameLabel->font(); + font.setBold(true); + font.setItalic(false); + m_nameLabel->setFont(font); + m_nameLabel->setText(name); + + m_infoLabel->setText(entry->author()); + + QString desc = entry->summary(m_lang); + desc.replace(QRegExp(QString::fromLatin1("\\n")), QString::fromLatin1("<br>")); + m_descLabel->setText(desc); + + InstallStatus installed = static_cast<Item*>(item_)->status(); + m_install->setText(installed == OldVersion ? i18n("Update Stuff", "Update") : i18n("Install")); + m_install->setEnabled(installed != Current); +} + +void Dialog::slotInstall() { + QListViewItem* item = m_listView->currentItem(); + if(!item) { + return; + } + + KNS::Entry* entry = m_entryMap[item]; + if(!entry) { + return; + } + + delete m_cursorSaver; + m_cursorSaver = new GUI::CursorSaver(); + setStatus(i18n("Installing item...")); + m_progress->show(); + m_timer->start(100); + connect(m_manager, SIGNAL(signalInstalled(KNS::Entry*)), SLOT(slotDoneInstall(KNS::Entry*))); + m_manager->install(m_type, entry); + delete m_cursorSaver; + m_cursorSaver = 0; +} + +void Dialog::slotDoneInstall(KNS::Entry* entry_) { + QMap<QListViewItem*, KNS::Entry*>::Iterator it; + for(it = m_entryMap.begin(); entry_ && it != m_entryMap.end(); ++it) { + if(it.data() == entry_) { + InstallStatus installed = Manager::installStatus(entry_); + static_cast<Item*>(it.key())->setStatus(installed); + m_install->setEnabled(installed != Current); + break; + } + } + delete m_cursorSaver; + m_cursorSaver = 0; + setStatus(i18n("Ready.")); + m_timer->stop(); + m_progress->hide(); +} + +void Dialog::slotMoveProgress() { + m_progress->setProgress(m_progress->progress()+5); +} + +void Dialog::setStatus(const QString& text_) { + m_statusBar->changeItem(QChar(' ') + text_, PROGRESS_STATUS_ID); +} + +void Dialog::slotShowPercent(KIO::Job*, unsigned long pct_) { + if(pct_ >= 100) { + m_progress->hide(); + } else { + m_progress->show(); + m_progress->setProgress(static_cast<int>(pct_), 100); + } +} + +void Dialog::slotPreviewResult(KIO::Job* job_) { + KIO::FileCopyJob* job = static_cast<KIO::FileCopyJob*>(job_); + if(job->error()) { + return; + } + QString tmpFile = job->destURL().path(); // might be different than m_tempPreviewImage->name() + QPixmap pix(tmpFile); + + if(!pix.isNull()) { + if(pix.width() > 64 || pix.height() > 64) { + pix.convertFromImage(pix.convertToImage().smoothScale(64, 64, QImage::ScaleMin)); + } + // only set label if it's still current + if(m_listView->selectedItem() == m_lastPreviewItem) { + m_iconLabel->setPixmap(pix); + } + } + delete m_tempPreviewImage; + m_tempPreviewImage = 0; +} + +#include "dialog.moc" diff --git a/src/newstuff/dialog.h b/src/newstuff/dialog.h new file mode 100644 index 0000000..f1c5e48 --- /dev/null +++ b/src/newstuff/dialog.h @@ -0,0 +1,101 @@ +/*************************************************************************** + copyright : (C) 2006 by Robby Stephenson + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of version 2 of the GNU General Public License as * + * published by the Free Software Foundation; * + * * + ***************************************************************************/ + +#ifndef TELLICO_NEWSTUFF_DIALOG_H +#define TELLICO_NEWSTUFF_DIALOG_H + +#include "manager.h" + +#include <kdialogbase.h> + +class KPushButton; +class KStatusBar; +namespace KIO { + class Job; +} +namespace KNS { + class Entry; + class Provider; +} + +class QProgressBar; +class QSplitter; +class QLabel; +class QTextEdit; + +namespace Tellico { + namespace GUI { + class ListView; + class CursorSaver; + } + + namespace NewStuff { + +class Dialog : public KDialogBase { +Q_OBJECT + +public: + Dialog(DataType type, QWidget* parent); + virtual ~Dialog(); + + QPtrList<DataSourceInfo> dataSourceInfo() const { return m_manager->dataSourceInfo(); } + +private slots: + void slotProviders(QPtrList<KNS::Provider>* list); + void slotData(KIO::Job* job, const QByteArray& data); + void slotResult(KIO::Job* job); + void slotPreviewResult(KIO::Job* job); + + void slotShowPercent(KIO::Job* job, unsigned long percent); + + void slotSelected(QListViewItem* item); + void slotInstall(); + void slotDoneInstall(KNS::Entry* entry); + + void slotProviderError(); + void slotMoveProgress(); + +private: + class Item; + + void setStatus(const QString& status); + void addEntry(KNS::Entry* entry); + + Manager* const m_manager; + DataType m_type; + QString m_lang; + QString m_typeName; + + QSplitter* m_split; + GUI::ListView* m_listView; + QLabel* m_iconLabel; + QLabel* m_nameLabel; + QLabel* m_infoLabel; + QTextEdit* m_descLabel; + KPushButton* m_install; + KStatusBar* m_statusBar; + QProgressBar* m_progress; + QTimer* m_timer; + GUI::CursorSaver* m_cursorSaver; + KTempFile* m_tempPreviewImage; + + QMap<KIO::Job*, KNS::Provider*> m_jobs; + QMap<KIO::Job*, QByteArray> m_data; + + QMap<QListViewItem*, KNS::Entry*> m_entryMap; + QListViewItem* m_lastPreviewItem; +}; + + } +} +#endif diff --git a/src/newstuff/manager.cpp b/src/newstuff/manager.cpp new file mode 100644 index 0000000..3b7efbf --- /dev/null +++ b/src/newstuff/manager.cpp @@ -0,0 +1,446 @@ +/*************************************************************************** + copyright : (C) 2006 by Robby Stephenson + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of version 2 of the GNU General Public License as * + * published by the Free Software Foundation; * + * * + ***************************************************************************/ + +#include "manager.h" +#include "newscript.h" +#include "../filehandler.h" +#include "../tellico_debug.h" +#include "../tellico_utils.h" +#include "../tellico_kernel.h" +#include "../fetch/fetch.h" + +#include <kurl.h> +#include <ktar.h> +#include <kglobal.h> +#include <kio/netaccess.h> +#include <kconfig.h> +#include <ktempfile.h> +#include <kio/job.h> +#include <kfileitem.h> +#include <kdeversion.h> +#include <knewstuff/entry.h> +#include <kstandarddirs.h> + +#include <qfileinfo.h> +#include <qdir.h> +#include <qptrstack.h> +#include <qvaluestack.h> +#include <qwidget.h> + +#include <sys/types.h> +#include <sys/stat.h> + +using Tellico::NewStuff::Manager; + +Manager::Manager(QObject* parent_) : QObject(parent_), m_tempFile(0) { + m_infoList.setAutoDelete(true); +} + +Manager::~Manager() { + delete m_tempFile; + m_tempFile = 0; +} + +bool Manager::installTemplate(const KURL& url_, const QString& entryName_) { + FileHandler::FileRef* ref = FileHandler::fileRef(url_); + + QString xslFile; + QStringList allFiles; + + bool success = true; + + // is there a better way to figure out if the url points to a XSL file or a tar archive + // than just trying to open it? + KTar archive(ref->fileName()); + if(archive.open(IO_ReadOnly)) { + const KArchiveDirectory* archiveDir = archive.directory(); + archiveDir->copyTo(Tellico::saveLocation(QString::fromLatin1("entry-templates/"))); + + allFiles = archiveFiles(archiveDir); + // remember files installed for template + xslFile = findXSL(archiveDir); + } else { // assume it's an xsl file + QString name = entryName_; + if(name.isEmpty()) { + name = url_.fileName(); + } + if(!name.endsWith(QString::fromLatin1(".xsl"))) { + name += QString::fromLatin1(".xsl"); + } + KURL dest; + dest.setPath(Tellico::saveLocation(QString::fromLatin1("entry-templates/")) + name); + success = true; + if(QFile::exists(dest.path())) { + myDebug() << "Manager::installTemplate() - " << dest.path() << " exists!" << endl; + success = false; + } else if(KIO::NetAccess::file_copy(url_, dest)) { + xslFile = dest.fileName(); + allFiles += xslFile; + } + } + + if(xslFile.isEmpty()) { + success = false; + } else { + // remove ".xsl" + xslFile.truncate(xslFile.length()-4); + KConfigGroup config(KGlobal::config(), "KNewStuffFiles"); + config.writeEntry(xslFile, allFiles); + m_urlNameMap.insert(url_, xslFile); + } + + checkCommonFile(); + + delete ref; + return success; +} + +bool Manager::removeTemplate(const QString& name_) { + KConfigGroup fileGroup(KGlobal::config(), "KNewStuffFiles"); + QStringList files = fileGroup.readListEntry(name_); + // at least, delete xsl file + if(files.isEmpty()) { + kdWarning() << "Manager::deleteTemplate() no file list found for " << name_ << endl; + files += name_; + } + + bool success = true; + QString path = Tellico::saveLocation(QString::fromLatin1("entry-templates/")); + for(QStringList::ConstIterator it = files.begin(); it != files.end(); ++it) { + if((*it).endsWith(QChar('/'))) { + // ok to not delete all directories + QDir().rmdir(path + *it); + } else { + success = success && QFile(path + *it).remove(); + if(!success) { + myDebug() << "Manager::removeTemplate() - failed to remove " << (path+*it) << endl; + } + } + } + + // remove config entries even if unsuccessful + fileGroup.deleteEntry(name_); + KConfigGroup statusGroup(KGlobal::config(), "KNewStuffStatus"); + QString entryName = statusGroup.readEntry(name_); + statusGroup.deleteEntry(name_); + statusGroup.deleteEntry(entryName); + + return success; +} + +bool Manager::installScript(const KURL& url_) { + FileHandler::FileRef* ref = FileHandler::fileRef(url_); + + KTar archive(ref->fileName()); + if(!archive.open(IO_ReadOnly)) { + myDebug() << "Manager::installScript() - can't open tar file" << endl; + return false; + } + + const KArchiveDirectory* archiveDir = archive.directory(); + + QString exeFile = findEXE(archiveDir); + if(exeFile.isEmpty()) { + myDebug() << "Manager::installScript() - no exe file found" << endl; + return false; + } + + QFileInfo exeInfo(exeFile); + DataSourceInfo* info = new DataSourceInfo(); + + QString copyTarget = Tellico::saveLocation(QString::fromLatin1("data-sources/")); + QString scriptFolder; + + // package could have a top-level directory or not + // it should have a directory... + const KArchiveEntry* firstEntry = archiveDir->entry(archiveDir->entries().first()); + if(firstEntry->isFile()) { + copyTarget += exeInfo.baseName(true) + '/'; + if(QFile::exists(copyTarget)) { + KURL u; + u.setPath(scriptFolder); + myLog() << "Manager::installScript() - deleting " << scriptFolder << endl; + KIO::NetAccess::del(u, Kernel::self()->widget()); + info->isUpdate = true; + } + scriptFolder = copyTarget; + } else { + scriptFolder = copyTarget + firstEntry->name() + '/'; + if(QFile::exists(copyTarget + exeFile)) { + info->isUpdate = true; + } + } + + // overwrites stuff there + archiveDir->copyTo(copyTarget); + + info->specFile = scriptFolder + exeInfo.baseName(true) + ".spec"; + if(!QFile::exists(info->specFile)) { + myDebug() << "Manager::installScript() - no spec file for script! " << info->specFile << endl; + delete info; + return false; + } + info->sourceName = exeFile; + info->sourceExec = copyTarget + exeFile; + m_infoList.append(info); + + KURL dest; + dest.setPath(info->sourceExec); + KFileItem item(KFileItem::Unknown, KFileItem::Unknown, dest, true); + ::chmod(QFile::encodeName(dest.path()), item.permissions() | S_IXUSR); + + { + KConfig spec(info->specFile, false, false); + // update name + info->sourceName = spec.readEntry("Name", info->sourceName); + spec.writePathEntry("ExecPath", info->sourceExec); + spec.writeEntry("NewStuffName", info->sourceName); + spec.writeEntry("DeleteOnRemove", true); + } + + { + KConfigGroup config(KGlobal::config(), "KNewStuffFiles"); + config.writeEntry(info->sourceName, archiveFiles(archiveDir)); + m_urlNameMap.insert(url_, info->sourceName); + } + + +// myDebug() << "Manager::installScript() - exeFile = " << exeFile << endl; +// myDebug() << "Manager::installScript() - sourceExec = " << info->sourceExec << endl; +// myDebug() << "Manager::installScript() - sourceName = " << info->sourceName << endl; +// myDebug() << "Manager::installScript() - specFile = " << info->specFile << endl; + + delete ref; + return true; +} + +bool Manager::removeScript(const QString& name_) { + KConfigGroup fileGroup(KGlobal::config(), "KNewStuffFiles"); + QStringList files = fileGroup.readListEntry(name_); + + bool success = true; + QString path = Tellico::saveLocation(QString::fromLatin1("data-sources/")); + for(QStringList::ConstIterator it = files.begin(); it != files.end(); ++it) { + if((*it).endsWith(QChar('/'))) { + // ok to not delete all directories + QDir().rmdir(path + *it); + } else { + success = success && QFile(path + *it).remove(); + if(!success) { + myDebug() << "Manager::removeScript() - failed to remove " << (path+*it) << endl; + } + } + } + + // remove config entries even if unsuccessful + fileGroup.deleteEntry(name_); + KConfigGroup statusGroup(KGlobal::config(), "KNewStuffStatus"); + QString entryName = statusGroup.readEntry(name_); + statusGroup.deleteEntry(name_); + statusGroup.deleteEntry(entryName); + + return success; +} + +Tellico::NewStuff::InstallStatus Manager::installStatus(KNS::Entry* entry_) { + KConfigGroup config(KGlobal::config(), "KNewStuffStatus"); + QString datestring = config.readEntry(entry_->name()); + if(datestring.isEmpty()) { + return NotInstalled; + } + + QDate date = QDate::fromString(datestring, Qt::ISODate); + if(!date.isValid()) { + return NotInstalled; + } + if(date < entry_->releaseDate()) { + return OldVersion; + } + + // also check that executable files exists + KConfigGroup fileGroup(KGlobal::config(), "KNewStuffFiles"); + QStringList files = fileGroup.readListEntry(entry_->name()); + QString path = Tellico::saveLocation(QString::fromLatin1("data-sources/")); + for(QStringList::ConstIterator it = files.begin(); it != files.end(); ++it) { + if(!QFile::exists(path + *it)) { + return NotInstalled; + } + } + return Current; +} + +QStringList Manager::archiveFiles(const KArchiveDirectory* dir_, const QString& path_) { + QStringList list; + + const QStringList dirEntries = dir_->entries(); + for(QStringList::ConstIterator it = dirEntries.begin(); it != dirEntries.end(); ++it) { + const QString& entry = *it; + const KArchiveEntry* curEntry = dir_->entry(entry); + if(curEntry->isFile()) { + list += path_ + entry; + } else if(curEntry->isDirectory()) { + list += archiveFiles(static_cast<const KArchiveDirectory*>(curEntry), path_ + entry + '/'); + // add directory AFTER contents, since we delete from the top down later + list += path_ + entry + '/'; + } + } + + return list; +} + +// don't recurse, the .xsl must be in top directory +QString Manager::findXSL(const KArchiveDirectory* dir_) { + const QStringList entries = dir_->entries(); + for(QStringList::ConstIterator it = entries.begin(); it != entries.end(); ++it) { + const QString& entry = *it; + if(entry.endsWith(QString::fromLatin1(".xsl"))) { + return entry; + } + } + return QString(); +} + +QString Manager::findEXE(const KArchiveDirectory* dir_) { + QPtrStack<KArchiveDirectory> dirStack; + QValueStack<QString> dirNameStack; + + dirStack.push(dir_); + dirNameStack.push(QString()); + + do { + const QString dirName = dirNameStack.pop(); + const KArchiveDirectory* curDir = dirStack.pop(); + const QStringList entries = curDir->entries(); + for(QStringList::ConstIterator it = entries.begin(); it != entries.end(); ++it) { + const QString& entry = *it; + const KArchiveEntry* archEntry = curDir->entry(entry); + + if(archEntry->isFile() && (archEntry->permissions() & S_IEXEC)) { + return dirName + entry; + } else if(archEntry->isDirectory()) { + dirStack.push(static_cast<const KArchiveDirectory*>(archEntry)); + dirNameStack.push(dirName + entry + '/'); + } + } + } while(!dirStack.isEmpty()); + + return QString(); +} + +void Manager::install(DataType type_, KNS::Entry* entry_) { + if(m_tempFile) { + delete m_tempFile; + } + m_tempFile = new KTempFile(); + m_tempFile->setAutoDelete(true); + + KURL destination; + destination.setPath(m_tempFile->name()); + KIO::FileCopyJob* job = KIO::file_copy(entry_->payload(), destination, -1, true); + connect(job, SIGNAL(result(KIO::Job*)), SLOT(slotDownloadJobResult(KIO::Job*))); + m_jobMap.insert(job, EntryPair(entry_, type_)); +} + +void Manager::slotDownloadJobResult(KIO::Job* job_) { + KIO::FileCopyJob* job = static_cast<KIO::FileCopyJob*>(job_); + if(job->error()) { + GUI::CursorSaver cs(Qt::arrowCursor); + delete m_tempFile; + m_tempFile = 0; + job->showErrorDialog(Kernel::self()->widget()); + emit signalInstalled(0); // still need to notify dialog that install failed + return; + } + + KNS::Entry* entry = m_jobMap[job_].first; + DataType type = m_jobMap[job_].second; + + bool deleteTempFile = true; + if(type == EntryTemplate) { + installTemplate(job->destURL(), entry->name()); + } else { +#if KDE_IS_VERSION(3,3,90) + // needed so the GPG signature can be checked + NewScript* newScript = new NewScript(this, Kernel::self()->widget()); + connect(newScript, SIGNAL(installFinished()), this, SLOT(slotInstallFinished())); + // need to delete temp file if install was not a success + // if it was a success, it gets deleted later + deleteTempFile = !newScript->install(job->destURL().path()); + m_scriptEntryMap.insert(newScript, entry); +#endif + // if failed, emit empty signal now + if(deleteTempFile) { + emit signalInstalled(0); + } + } + if(deleteTempFile) { + delete m_tempFile; + m_tempFile = 0; + } +} + +void Manager::slotInstallFinished() { + const NewScript* newScript = ::qt_cast<const NewScript*>(sender()); + if(newScript && newScript->successfulInstall()) { + const QString name = m_urlNameMap[newScript->url()]; + KNS::Entry* entry = m_scriptEntryMap[newScript]; + KConfigGroup config(KGlobal::config(), "KNewStuffStatus"); + // have to keep a config entry that maps the name of the file to the name + // of the newstuff entry, so we can track which entries are installed + // name and entry-name() are probably the same for scripts, but not a problem + config.writeEntry(name, entry->name()); + config.writeEntry(entry->name(), entry->releaseDate().toString(Qt::ISODate)); + config.sync(); + emit signalInstalled(entry); + } else { + emit signalInstalled(0); + kdWarning() << "Manager::slotInstallFinished() - Failed to install" << endl; + } + delete m_tempFile; + m_tempFile = 0; +} + +bool Manager::checkCommonFile() { + // look for a file that gets installed to know the installation directory + // need to check timestamps + QString userDataDir = Tellico::saveLocation(QString::null); + QString userCommonFile = userDataDir + '/' + QString::fromLatin1("tellico-common.xsl"); + if(QFile::exists(userCommonFile)) { + // check timestamps + // pics/tellico.png is not likely to be in a user directory + QString installDir = KGlobal::dirs()->findResourceDir("appdata", QString::fromLatin1("pics/tellico.png")); + QString installCommonFile = installDir + '/' + QString::fromLatin1("tellico-common.xsl"); +#ifndef NDEBUG + if(userCommonFile == installCommonFile) { + kdWarning() << "Manager::checkCommonFile() - install location is same as user location" << endl; + } +#endif + QFileInfo installInfo(installCommonFile); + QFileInfo userInfo(userCommonFile); + if(installInfo.lastModified() > userInfo.lastModified()) { + // the installed file has been modified more recently than the user's + // remove user's tellico-common.xsl file so it gets copied again + myLog() << "Manager::checkCommonFile() - removing " << userCommonFile << endl; + myLog() << "Manager::checkCommonFile() - copying " << installCommonFile << endl; + QFile::remove(userCommonFile); + } else { + return true; + } + } + KURL src, dest; + src.setPath(KGlobal::dirs()->findResource("appdata", QString::fromLatin1("tellico-common.xsl"))); + dest.setPath(userCommonFile); + return KIO::NetAccess::file_copy(src, dest); +} + +#include "manager.moc" diff --git a/src/newstuff/manager.h b/src/newstuff/manager.h new file mode 100644 index 0000000..4102be3 --- /dev/null +++ b/src/newstuff/manager.h @@ -0,0 +1,101 @@ +/*************************************************************************** + copyright : (C) 2006 by Robby Stephenson + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of version 2 of the GNU General Public License as * + * published by the Free Software Foundation; * + * * + ***************************************************************************/ + +#ifndef TELLICO_NEWSTUFF_MANAGER_H +#define TELLICO_NEWSTUFF_MANAGER_H + +class KArchiveDirectory; +class KURL; +class KTempFile; + +#include <qobject.h> +#include <qptrlist.h> + +class QStringList; + +namespace KNS { + class Entry; +} +namespace KIO { + class Job; +} + +namespace Tellico { + namespace NewStuff { + +class NewScript; + +enum DataType { + EntryTemplate, + DataScript +}; + +enum InstallStatus { + NotInstalled, + OldVersion, + Current +}; + +struct DataSourceInfo { + DataSourceInfo() : isUpdate(false) {} + QString specFile; // full path of .spec file + QString sourceName; + QString sourceExec; // full executable path of script + bool isUpdate : 1; // whether the info is for an updated source +}; + +class Manager : public QObject { +Q_OBJECT + +public: + Manager(QObject* parent); + ~Manager(); + + void install(DataType type, KNS::Entry* entry); + QPtrList<DataSourceInfo> dataSourceInfo() const { return m_infoList; } + + bool installTemplate(const KURL& url, const QString& entryName = QString::null); + bool removeTemplate(const QString& name); + + bool installScript(const KURL& url); + bool removeScript(const QString& name); + + static InstallStatus installStatus(KNS::Entry* entry); + static bool checkCommonFile(); + +signals: + void signalInstalled(KNS::Entry* entry); + +private slots: + void slotDownloadJobResult(KIO::Job* job); + void slotInstallFinished(); + +private: + static QStringList archiveFiles(const KArchiveDirectory* dir, + const QString& path = QString::null); + + static QString findXSL(const KArchiveDirectory* dir); + static QString findEXE(const KArchiveDirectory* dir); + + typedef QPair<KNS::Entry*, DataType> EntryPair; + QMap<KIO::Job*, EntryPair> m_jobMap; + QMap<KURL, QString> m_urlNameMap; + QMap<const NewScript*, KNS::Entry*> m_scriptEntryMap; + QPtrList<DataSourceInfo> m_infoList; + KTempFile* m_tempFile; +}; + + } +} + +#endif diff --git a/src/newstuff/newscript.cpp b/src/newstuff/newscript.cpp new file mode 100644 index 0000000..045f881 --- /dev/null +++ b/src/newstuff/newscript.cpp @@ -0,0 +1,48 @@ +/*************************************************************************** + copyright : (C) 2006 by Robby Stephenson + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of version 2 of the GNU General Public License as * + * published by the Free Software Foundation; * + * * + ***************************************************************************/ + +#include "newscript.h" +#include "manager.h" + +#include <kurl.h> + +#include <qwidget.h> + +using Tellico::NewStuff::NewScript; + +NewScript::NewScript(Manager* manager_, QWidget* parentWidget_) +#if KDE_IS_VERSION(3,3,90) + : KNewStuffSecure(QString::fromLatin1("tellico/data-source"), parentWidget_) +#else + : QObject(parentWidget_) +#endif + , m_manager(manager_), m_success(false) { +} + +void NewScript::installResource() { + // m_tarName is protected in superclass + KURL u; + u.setPath(m_tarName); + m_success = m_manager->installScript(u); + m_url = u; +} + +#if KDE_IS_VERSION(3,3,90) +#include <knewstuff/knewstuffsecure.h> +#define SUPERCLASS KNewStuffSecure +#else +#define SUPERCLASS QObject +#endif + +#include "newscript.moc" +#undef SUPERCLASS diff --git a/src/newstuff/newscript.h b/src/newstuff/newscript.h new file mode 100644 index 0000000..8bc3154 --- /dev/null +++ b/src/newstuff/newscript.h @@ -0,0 +1,60 @@ +/*************************************************************************** + copyright : (C) 2006 by Robby Stephenson + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of version 2 of the GNU General Public License as * + * published by the Free Software Foundation; * + * * + ***************************************************************************/ + +#ifndef TELLICO_NEWSTUFF_NEWSCRIPT_H +#define TELLICO_NEWSTUFF_NEWSCRIPT_H + +#include <kdeversion.h> +#include <kurl.h> + +#if KDE_IS_VERSION(3,3,90) +#include <knewstuff/knewstuffsecure.h> +#define SUPERCLASS KNewStuffSecure +#else +#define SUPERCLASS QObject +#endif + +#include <qobject.h> + +namespace Tellico { + namespace NewStuff { + +class Manager; + +class NewScript : public SUPERCLASS { +Q_OBJECT + +public: + NewScript(Manager* manager, QWidget* parentWidget = 0); + virtual ~NewScript() {} + + const KURL& url() const { return m_url; } + bool successfulInstall() const { return m_success; } + +private: + virtual void installResource(); + + Manager* m_manager; +#if !KDE_IS_VERSION(3,3,90) + // KNewStuffSecure has a protected variable + QString m_tarName; +#endif + KURL m_url; + bool m_success : 1; +}; + + } +} + +#undef SUPERCLASS +#endif diff --git a/src/newstuff/providerloader.cpp b/src/newstuff/providerloader.cpp new file mode 100644 index 0000000..b3f95ae --- /dev/null +++ b/src/newstuff/providerloader.cpp @@ -0,0 +1,102 @@ +/*************************************************************************** + copyright : (C) 2006 by Robby Stephenson + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of version 2 of the GNU General Public License as * + * published by the Free Software Foundation; * + * * + ***************************************************************************/ + +// this class is largely copied from kdelibs/knewstuff/provider.cpp +// which is Copyright (c) 2002 Cornelius Schumacher <[email protected]> +// and licensed under GPL v2, just like Tellico + +#include "providerloader.h" +#include "../tellico_debug.h" +#include "../latin1literal.h" + +#include <kio/job.h> +#include <knewstuff/provider.h> +#include <kglobal.h> +#include <kconfig.h> +#include <kmessagebox.h> +#include <klocale.h> + +#include <qdom.h> + +using Tellico::NewStuff::ProviderLoader; + +ProviderLoader::ProviderLoader( QWidget *parentWidget ) : + mParentWidget( parentWidget ), mTryAlt(true) +{ + mProviders.setAutoDelete( true ); +} + +void ProviderLoader::load( const QString &type, const QString &providersList ) +{ + mProviders.clear(); + mJobData.truncate(0); + +// myLog() << "ProviderLoader::load(): providersList: " << providersList << endl; + + KIO::TransferJob *job = KIO::get( KURL( providersList ), false, false ); + connect( job, SIGNAL( result( KIO::Job * ) ), + SLOT( slotJobResult( KIO::Job * ) ) ); + connect( job, SIGNAL( data( KIO::Job *, const QByteArray & ) ), + SLOT( slotJobData( KIO::Job *, const QByteArray & ) ) ); + connect( job, SIGNAL( percent (KIO::Job *, unsigned long) ), + SIGNAL( percent (KIO::Job *, unsigned long) ) ); + +// job->dumpObjectInfo(); +} + +void ProviderLoader::slotJobData( KIO::Job *, const QByteArray &data ) +{ + if ( data.size() == 0 ) return; + QCString str( data, data.size() + 1 ); + mJobData.append( QString::fromUtf8( str ) ); +} + +void ProviderLoader::slotJobResult( KIO::Job *job ) +{ + if ( job->error() ) { + job->showErrorDialog( mParentWidget ); + if(mTryAlt && !mAltProvider.isEmpty()) { + mTryAlt = false; + load(QString(), mAltProvider); + } else { + emit error(); + } + return; + } + + QDomDocument doc; + if ( !doc.setContent( mJobData ) ) { + myDebug() << "ProviderLoader::slotJobResult() - error parsing providers list." << endl; + if(mTryAlt && !mAltProvider.isEmpty()) { + mTryAlt = false; + load(QString(), mAltProvider); + } else { + emit error(); + } + return; + } + + QDomElement providers = doc.documentElement(); + QDomNode n; + for ( n = providers.firstChild(); !n.isNull(); n = n.nextSibling() ) { + QDomElement p = n.toElement(); + + if ( p.tagName() == Latin1Literal("provider") ) { + mProviders.append( new KNS::Provider( p ) ); + } + } + + emit providersLoaded( &mProviders ); +} + +#include "providerloader.moc" diff --git a/src/newstuff/providerloader.h b/src/newstuff/providerloader.h new file mode 100644 index 0000000..3d2968c --- /dev/null +++ b/src/newstuff/providerloader.h @@ -0,0 +1,84 @@ +/*************************************************************************** + copyright : (C) 2006 by Robby Stephenson + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of version 2 of the GNU General Public License as * + * published by the Free Software Foundation; * + * * + ***************************************************************************/ + +// this class is largely copied from kdelibs/knewstuff/provider.h +// which is Copyright (c) 2002 Cornelius Schumacher <[email protected]> +// and licensed under GPL v2, just like Tellico +// +// I want progress info for the download, and this was the +// easiest way to get it + +#ifndef TELLICO_NEWSTUFF_PROVIDERLOADER_H +#define TELLICO_NEWSTUFF_PROVIDERLOADER_H + +#include <qobject.h> +#include <qptrlist.h> + +namespace KIO { + class Job; +} +namespace KNS { + class Provider; +} + +namespace Tellico { + namespace NewStuff { + +class ProviderLoader : public QObject { +Q_OBJECT +public: + /** + * Constructor. + * + * @param parentWidget the parent widget + */ + ProviderLoader( QWidget *parentWidget ); + + /** + * Starts asynchronously loading the list of providers of the + * specified type. + * + * @param type data type such as 'kdesktop/wallpaper'. + * @param providerList the URl to the list of providers; if empty + * we first try the ProvidersUrl from KGlobal::config, then we + * fall back to a hardcoded value. + */ + void load( const QString &type, const QString &providerList = QString::null ); + + void setAlternativeProvider(const QString& alt) { mAltProvider = alt; } + + signals: + /** + * Indicates that the list of providers has been successfully loaded. + */ + void providersLoaded( QPtrList<KNS::Provider>* ); + void percent(KIO::Job *job, unsigned long percent); + void error(); + + protected slots: + void slotJobData( KIO::Job *, const QByteArray & ); + void slotJobResult( KIO::Job * ); + + private: + QWidget *mParentWidget; + + QString mJobData; + + QPtrList<KNS::Provider> mProviders; + QString mAltProvider; + bool mTryAlt; +}; + + } +} +#endif |