diff options
Diffstat (limited to 'kbugbuster/backend')
48 files changed, 5873 insertions, 0 deletions
diff --git a/kbugbuster/backend/Makefile.am b/kbugbuster/backend/Makefile.am new file mode 100644 index 00000000..e1bfd266 --- /dev/null +++ b/kbugbuster/backend/Makefile.am @@ -0,0 +1,16 @@ +INCLUDES= -I$(srcdir)/.. $(all_includes) + +noinst_LTLIBRARIES = libkbbbackend.la + +libkbbbackend_la_SOURCES = packagelistjob.cpp bugjob.cpp \ + package.cpp bugsystem.cpp bug.cpp bugdetails.cpp \ + bugcommand.cpp buglistjob.cpp bugmybugsjob.cpp \ + mailsender.cpp bugcache.cpp bugdetailsjob.cpp \ + person.cpp smtp.cpp bugserver.cpp \ + bugserverconfig.cpp \ + processor.cpp \ + domprocessor.cpp rdfprocessor.cpp htmlparser.cpp \ + kbbprefs.cpp + +METASOURCES = AUTO + diff --git a/kbugbuster/backend/bug.cpp b/kbugbuster/backend/bug.cpp new file mode 100644 index 00000000..4523652e --- /dev/null +++ b/kbugbuster/backend/bug.cpp @@ -0,0 +1,240 @@ + +#include "bug.h" + +#include "bugimpl.h" + +#include <assert.h> +#include <kdebug.h> + +Bug::Bug() +: m_impl( NULL ) +{ +} + +Bug::Bug( BugImpl *impl ) +: m_impl( impl ) +{ +} + +Bug::Bug( const Bug &other ) +{ + (*this) = other; +} + +Bug Bug::fromNumber( const QString &bugNumber ) +{ + return new BugImpl( QString::null, Person(), bugNumber, 0xFFFFFFFF, Normal, Person(), + Unconfirmed, Bug::BugMergeList() ); +} + +Bug &Bug::operator=( const Bug &rhs ) +{ + m_impl = rhs.m_impl; + return *this; +} + +Bug::~Bug() +{ +} + +QString Bug::severityLabel( Bug::Severity s ) +{ + switch ( s ) + { + case Critical: return i18n("Critical"); + case Grave: return i18n("Grave"); + case Major: return i18n("Major"); + case Crash: return i18n("Crash"); + case Normal: return i18n("Normal"); + case Minor: return i18n("Minor"); + case Wishlist: return i18n("Wishlist"); + default: + case SeverityUndefined: return i18n("Undefined"); + } +} + +QString Bug::severityToString( Bug::Severity s ) +{ + switch ( s ) { + case Critical: return QString::fromLatin1( "critical" ); + case Grave: return QString::fromLatin1( "grave" ); + case Major: return QString::fromLatin1( "major" ); + case Crash: return QString::fromLatin1( "crash" ); + case Normal: return QString::fromLatin1( "normal" ); + case Minor: return QString::fromLatin1( "minor" ); + case Wishlist: return QString::fromLatin1( "wishlist" ); + default: + kdWarning() << "Bug::severityToString invalid severity " << s << endl; + return QString::fromLatin1( "<invalid>" ); + } +} + +Bug::Severity Bug::stringToSeverity( const QString &s, bool *ok ) +{ + if ( ok ) + *ok = true; + + if ( s == "critical" ) return Critical; + else if ( s == "grave" ) return Grave; + else if ( s == "major" ) return Major; + else if ( s == "crash" || s == "drkonqi" ) return Crash; + else if ( s == "normal" ) return Normal; + else if ( s == "minor" ) return Minor; + else if ( s == "wishlist" ) return Wishlist; + + kdWarning() << "Bug::stringToSeverity: invalid severity: " << s << endl; + if ( ok ) + *ok = false; + return SeverityUndefined; +} + +QValueList<Bug::Severity> Bug::severities() +{ + QValueList<Severity> s; + s << Critical << Grave << Major << Crash << Normal << Minor << Wishlist; + return s; +} + +QString Bug::statusLabel( Bug::Status s ) +{ + switch ( s ) + { + case Unconfirmed: return i18n("Unconfirmed"); + case New: return i18n("New"); + case Assigned: return i18n("Assigned"); + case Reopened: return i18n("Reopened"); + case Closed: return i18n("Closed"); + default: + case StatusUndefined: return i18n("Undefined"); + } +} + +QString Bug::statusToString( Bug::Status s ) +{ + switch ( s ) { + case Unconfirmed: return QString::fromLatin1( "unconfirmed" ); + case New: return QString::fromLatin1( "new" ); + case Assigned: return QString::fromLatin1( "assigned" ); + case Reopened: return QString::fromLatin1( "reopened" ); + case Closed: return QString::fromLatin1( "closed" ); + default: + kdWarning() << "Bug::statusToString invalid status " << s << endl; + return QString::fromLatin1( "<invalid>" ); + } +} + +Bug::Status Bug::stringToStatus( const QString &s, bool *ok ) +{ + if ( ok ) + *ok = true; + + if ( s == "unconfirmed" ) return Unconfirmed; + else if ( s == "new" ) return New; + else if ( s == "assigned" ) return Assigned; + else if ( s == "reopened" ) return Reopened; + else if ( s == "closed" ) return Closed; + + kdWarning() << "Bug::stringToStatus: invalid status: " << s << endl; + if ( ok ) + *ok = false; + return StatusUndefined; +} + +QString Bug::title() const +{ + if ( !m_impl ) + return QString::null; + + return m_impl->title; +} + +void Bug::setTitle( QString title) +{ + if ( m_impl ) + m_impl->title = title; +} + +uint Bug::age() const +{ + if ( !m_impl ) + return 0; + + return m_impl->age; +} + +void Bug::setAge( uint age ) +{ + if ( m_impl ) + m_impl->age = age; +} + +struct Person Bug::submitter() const +{ + if ( !m_impl ) + return Person( QString::null, QString::null ); + + return m_impl->submitter; +} + +QString Bug::number() const +{ + if ( !m_impl ) + return QString::null; + + return m_impl->number; +} + +Bug::Severity Bug::severity() const +{ + if ( !m_impl ) + return Normal; + + return m_impl->severity; +} + +void Bug::setSeverity( Bug::Severity severity ) +{ + if ( m_impl ) + m_impl->severity = severity; +} + +Bug::BugMergeList Bug::mergedWith() const +{ + if ( !m_impl ) + return Bug::BugMergeList(); + + return m_impl->mergedWith; +} + + +Bug::Status Bug::status() const +{ + if ( !m_impl ) + return StatusUndefined; + + return m_impl->status; +} + +QString Bug::severityAsString() const +{ + return severityToString( severity() ); +} + +Person Bug::developerTODO() const +{ + return (m_impl == NULL) ? Person( QString::null, QString::null ) : + m_impl->developerTODO; +} + +bool Bug::operator==( const Bug &rhs ) +{ + return m_impl == rhs.m_impl; +} + +bool Bug::operator<( const Bug &rhs ) const +{ + return m_impl < rhs.m_impl; +} + +/* vim: set ts=4 sw=4 et softtabstop=4: */ + diff --git a/kbugbuster/backend/bug.h b/kbugbuster/backend/bug.h new file mode 100644 index 00000000..9a5ae8b6 --- /dev/null +++ b/kbugbuster/backend/bug.h @@ -0,0 +1,92 @@ +#ifndef __bug_h__ +#define __bug_h__ + +#include "person.h" + +#include <qvaluelist.h> + +#include <ksharedptr.h> + +class BugImpl; + +class Bug +{ +public: + typedef QValueList<Bug> List; + typedef QValueList<int> BugMergeList; + + enum Severity { SeverityUndefined, Critical, Grave, Major, Crash, Normal, + Minor, Wishlist }; + enum Status { StatusUndefined, Unconfirmed, New, Assigned, Reopened, + Closed }; + + Bug(); + Bug( BugImpl *impl ); + Bug( const Bug &other ); + Bug &operator=( const Bug &rhs ); + ~Bug(); + + static QString severityLabel( Severity s ); + /** + Return string representation of severity. This function is symmetric to + stringToSeverity(). + */ + static QString severityToString( Severity s ); + /** + Return severity code of string representation. This function is symmetric + to severityToString(). + */ + static Severity stringToSeverity( const QString &, bool *ok = 0 ); + + static QValueList<Severity> severities(); + + uint age() const; + void setAge( uint days ); + + QString title() const; + void setTitle( QString title ); + Person submitter() const; + QString number() const; + Severity severity() const; + void setSeverity( Severity severity ); + QString severityAsString() const; + Person developerTODO() const; + + BugMergeList mergedWith() const; + + /** + * Status of a bug. Currently open or closed. + * TODO: Should we add a status 'deleted' here ? + */ + Status status() const; + void setStatus( Status newStatus ); + + static QString statusLabel( Status s ); + /** + Return string representation of status. This function is symmetric to + stringToStatus(). + */ + static QString statusToString( Status s ); + /** + Return status code of string representation. This function is symmetric + to statusToString(). + */ + static Status stringToStatus( const QString &, bool *ok = 0 ); + + bool operator==( const Bug &rhs ); + bool operator<( const Bug &rhs ) const; + + bool isNull() const { return m_impl == 0; } + + static Bug fromNumber( const QString &bugNumber ); + +private: + BugImpl *impl() const { return m_impl; } + + KSharedPtr<BugImpl> m_impl; +}; + +#endif + +/* vim: set sw=4 ts=4 et softtabstop=4: */ + diff --git a/kbugbuster/backend/bugcache.cpp b/kbugbuster/backend/bugcache.cpp new file mode 100644 index 00000000..0eb44c8c --- /dev/null +++ b/kbugbuster/backend/bugcache.cpp @@ -0,0 +1,289 @@ +// (C) 2001, Cornelius Schumacher + +#include <qstringlist.h> +#include <qfile.h> + +#include <ksimpleconfig.h> +#include <kstandarddirs.h> +#include <kurl.h> +#include <kdebug.h> + +#include "packageimpl.h" +#include "bugimpl.h" +#include "bugdetailsimpl.h" + +#include "bugcache.h" + +BugCache::BugCache( const QString &id ) +{ + mId = id; + + init(); +} + +BugCache::~BugCache() +{ + m_cachePackages->sync(); + m_cacheBugs->sync(); + + delete m_cachePackages; + delete m_cacheBugs; +} + +void BugCache::init() +{ + mCachePackagesFileName = locateLocal( "appdata", mId + "-packages.cache" ); + mCacheBugsFileName = locateLocal( "appdata", mId + "-bugs.cache" ); + + m_cachePackages = new KSimpleConfig( mCachePackagesFileName ); + m_cacheBugs = new KSimpleConfig( mCacheBugsFileName ); +} + +void BugCache::savePackageList( const Package::List &pkgs ) +{ + Package::List::ConstIterator it; + for (it = pkgs.begin(); it != pkgs.end(); ++it) { + m_cachePackages->setGroup((*it).name()); + m_cachePackages->writeEntry("description",(*it).description()); + m_cachePackages->writeEntry("numberOfBugs",(*it).numberOfBugs()); + m_cachePackages->writeEntry("components",(*it).components()); + writePerson(m_cachePackages,"Maintainer",(*it).maintainer()); + } +} + +Package::List BugCache::loadPackageList() +{ + Package::List pkgs; + + QStringList packages = m_cachePackages->groupList(); + QStringList::ConstIterator it; + for( it = packages.begin(); it != packages.end(); ++it ) { + if ((*it) == "<default>") continue; + if ((*it).contains("/")) continue; + + m_cachePackages->setGroup(*it); + + QString description = m_cachePackages->readEntry("description"); + int numberOfBugs = m_cachePackages->readNumEntry("numberOfBugs"); + Person maintainer = readPerson( m_cachePackages, "Maintainer"); + QStringList components = m_cachePackages->readListEntry("components"); + + pkgs.append( Package( new PackageImpl( (*it), description, numberOfBugs, + maintainer, components ) ) ); + } + + return pkgs; +} + +void BugCache::invalidatePackageList() +{ + // Completely wipe out packages.cache + QStringList packages = m_cachePackages->groupList(); + QStringList::ConstIterator it; + for( it = packages.begin(); it != packages.end(); ++it ) { + if ((*it) == "<default>") continue; + m_cachePackages->deleteGroup(*it, true); + } +} + +void BugCache::saveBugList( const Package &pkg, const QString &component, const Bug::List &bugs ) +{ + QStringList bugList; + + Bug::List::ConstIterator it; + for( it = bugs.begin(); it != bugs.end(); ++it ) { + QString number = (*it).number(); + bugList.append( number ); + m_cacheBugs->setGroup( number ); + m_cacheBugs->writeEntry( "Title", (*it).title() ); + m_cacheBugs->writeEntry( "Severity", Bug::severityToString((*it).severity()) ); + m_cacheBugs->writeEntry( "Status", Bug::statusToString((*it).status()) ); + m_cacheBugs->writeEntry( "MergedWith" , (*it).mergedWith() ); + m_cacheBugs->writeEntry( "Age", ( *it ).age() ); + writePerson( m_cacheBugs, "Submitter", (*it).submitter() ); + writePerson( m_cacheBugs, "TODO", (*it).developerTODO() ); + } + + if ( component.isEmpty() ) + m_cachePackages->setGroup( pkg.name() ); + else { + m_cachePackages->setGroup( pkg.name() + "/" + component ); + } + + m_cachePackages->writeEntry( "bugList", bugList ); +} + +Bug::List BugCache::loadBugList( const Package &pkg, const QString &component, bool disconnected ) +{ +// kdDebug() << "Loading bug list for " << pkg.name() << endl; + + Bug::List bugList; + + if ( component.isEmpty() ) + m_cachePackages->setGroup( pkg.name() ); + else + m_cachePackages->setGroup( pkg.name() + "/" + component ); + + QStringList bugs = m_cachePackages->readListEntry( "bugList" ); + +// kdDebug() << " Bugs: " << (bugs.join(",")) << endl; + + QStringList::ConstIterator it; + for( it = bugs.begin(); it != bugs.end(); ++it ) { + if ( m_cacheBugs->hasGroup(*it) ) + { + m_cacheBugs->setGroup(*it); + QString title = m_cacheBugs->readEntry("Title"); + if ( !title.isEmpty() ) // dunno how I ended up with an all empty bug in the cache + { + Person submitter = readPerson( m_cacheBugs, "Submitter" ); + Bug::Status status = Bug::stringToStatus( m_cacheBugs->readEntry("Status") ); + Bug::Severity severity = Bug::stringToSeverity( m_cacheBugs->readEntry("Severity") ); + Person developerTODO = readPerson( m_cacheBugs, "TODO" ); + Bug::BugMergeList mergedWith = m_cacheBugs->readIntListEntry( "MergedWith" ); + uint age = m_cacheBugs->readUnsignedNumEntry( "Age", 0xFFFFFFFF ); + bugList.append( Bug( new BugImpl( title, submitter, ( *it ), age, + severity, developerTODO, + status, mergedWith ) ) ); + } + } else { + // This bug is in the package cache's buglist but not in the bugs cache + // Probably a new bug, we need to fetch it - if we're not in disconnected mode + kdWarning() << "Bug " << *it << " not in bug cache" << endl; + if ( !disconnected ) + return Bug::List(); // returning an empty list will trigger a reload of the buglist + } + } + + return bugList; +} + +void BugCache::invalidateBugList( const Package& pkg, const QString &component ) +{ + kdDebug() << "BugCache::invalidateBugList " << pkg.name() + << " (" << component << ")" << endl; + + // Erase bug list for this package + if ( component.isEmpty() ) { + m_cachePackages->setGroup( pkg.name() ); + } else { + QString key = pkg.name() + "/" + component; + m_cachePackages->setGroup( key ); + m_cachePackages->setGroup( pkg.name() + "/" + component ); + } + + m_cachePackages->writeEntry("bugList",QString::null); +} + +void BugCache::saveBugDetails( const Bug &bug, const BugDetails &details ) +{ + m_cacheBugs->setGroup( bug.number() ); + + m_cacheBugs->writeEntry( "Version", details.version() ); + m_cacheBugs->writeEntry( "Source", details.source() ); + m_cacheBugs->writeEntry( "Compiler", details.compiler() ); + m_cacheBugs->writeEntry( "OS", details.os() ); + + QStringList senders; + QStringList texts; + QStringList dates; + + BugDetailsPart::List parts = details.parts(); + BugDetailsPart::List::ConstIterator it; + for ( it = parts.begin(); it != parts.end(); ++it ) { + senders.append( (*it).sender.fullName() ); + texts.append( (*it).text ); + dates.append( (*it).date.toString( Qt::ISODate ) ); + } + + m_cacheBugs->writeEntry( "Details", texts ); + m_cacheBugs->writeEntry( "Senders", senders ); + m_cacheBugs->writeEntry( "Dates", dates ); +} + +bool BugCache::hasBugDetails( const Bug& bug ) const +{ + if ( !m_cacheBugs->hasGroup( bug.number() ) ) + return false; + + m_cacheBugs->setGroup( bug.number() ); + return m_cacheBugs->hasKey( "Details" ); +} + +BugDetails BugCache::loadBugDetails( const Bug &bug ) +{ + if ( !m_cacheBugs->hasGroup( bug.number() ) ) { + return BugDetails(); + } + + m_cacheBugs->setGroup( bug.number() ); + + BugDetailsPart::List parts; + + QStringList texts = m_cacheBugs->readListEntry( "Details" ); + QStringList senders = m_cacheBugs->readListEntry( "Senders" ); + QStringList dates = m_cacheBugs->readListEntry( "Dates" ); + + QStringList::ConstIterator itTexts = texts.begin(); + QStringList::ConstIterator itSenders = senders.begin(); + QStringList::ConstIterator itDates = dates.begin(); + while( itTexts != texts.end() ) { + QDateTime date = QDateTime::fromString( *itDates, Qt::ISODate ); + parts.append( BugDetailsPart( Person(*itSenders), date, *itTexts ) ); + + ++itTexts; + ++itSenders; + ++itDates; + } + + if ( parts.count() == 0 ) { + return BugDetails(); + } + + QString version = m_cacheBugs->readEntry( "Version" ); + QString source = m_cacheBugs->readEntry( "Source" ); + QString compiler = m_cacheBugs->readEntry( "Compiler" ); + QString os = m_cacheBugs->readEntry( "OS" ); + + return BugDetails( new BugDetailsImpl( version, source, compiler, os, + parts ) ); +} + +void BugCache::invalidateBugDetails( const Bug& bug ) +{ + m_cacheBugs->deleteGroup( bug.number(), true ); +} + +void BugCache::clear() +{ + delete m_cachePackages; + delete m_cacheBugs; + + QFile f1( mCachePackagesFileName ); + f1.remove(); + + QFile f2( mCacheBugsFileName ); + f2.remove(); + + init(); +} + +void BugCache::writePerson( KSimpleConfig *file, const QString &key, + const Person &p ) +{ + QStringList values; + values.append(p.name); + values.append(p.email); + file->writeEntry( key, values ); +} + +struct Person BugCache::readPerson( KSimpleConfig *file, const QString &key ) +{ + struct Person p; + QStringList values = file->readListEntry(key); + if ( values.count() > 0 ) + p.name = values[0]; + if ( values.count() > 1 ) + p.email = values[1]; + return p; +} diff --git a/kbugbuster/backend/bugcache.h b/kbugbuster/backend/bugcache.h new file mode 100644 index 00000000..af1aed11 --- /dev/null +++ b/kbugbuster/backend/bugcache.h @@ -0,0 +1,47 @@ +#ifndef BUGCACHE_H +#define BUGCACHE_H + +class KSimpleConfig; + +#include "package.h" +#include "bug.h" +#include "bugdetails.h" + +class BugCache +{ + public: + BugCache( const QString &id ); + ~BugCache(); + + void savePackageList( const Package::List &pkgs ); + Package::List loadPackageList(); + void invalidatePackageList(); + + void saveBugList( const Package &pkg, const QString &component, const Bug::List & ); + Bug::List loadBugList( const Package &pkg, const QString &component, bool disconnected ); + void invalidateBugList( const Package &pkg, const QString &component ); + + void saveBugDetails( const Bug &bug, const BugDetails & ); + BugDetails loadBugDetails( const Bug &bug ); + void invalidateBugDetails( const Bug &bug ); + bool hasBugDetails( const Bug& bug ) const; + + void clear(); + + private: + void init(); + + void writePerson( KSimpleConfig *file, const QString &key, + const Person &p ); + struct Person readPerson (KSimpleConfig *file, const QString &key ); + + QString mId; + + KSimpleConfig *m_cachePackages; + KSimpleConfig *m_cacheBugs; + + QString mCachePackagesFileName; + QString mCacheBugsFileName; +}; + +#endif diff --git a/kbugbuster/backend/bugcommand.cpp b/kbugbuster/backend/bugcommand.cpp new file mode 100644 index 00000000..332c937d --- /dev/null +++ b/kbugbuster/backend/bugcommand.cpp @@ -0,0 +1,317 @@ +#include <kdebug.h> +#include <kconfig.h> +#include <klocale.h> + +#include "bugcommand.h" + +QString BugCommand::name() +{ + return i18n("Unknown"); +} + +QString BugCommand::details() +{ + return QString::null; +} + +BugCommand *BugCommand::load( KConfig *config, const QString &type ) +{ + QString bugNumber = config->group(); + // ### this sucks. we better let Bug implement proper persistance, + // because this way of instantiating a bug object doesn't bring back + // properties like title, package, etc. (Simon) + Bug bug = Bug::fromNumber( bugNumber ); + Package pkg = Package(); // hack + + if ( type == "Close" ) { + return new BugCommandClose( bug, config->readEntry( type ), pkg ); + } else if ( type == "Reopen" ) { + return new BugCommandReopen( bug, pkg ); + } else if ( type == "Merge" ) { + return new BugCommandMerge( config->readListEntry( type ), pkg ); + } else if ( type == "Unmerge" ) { + return new BugCommandUnmerge( bug, pkg ); + } else if ( type == "Reassign" ) { + return new BugCommandReassign( bug, config->readEntry( type ), pkg ); + } else if ( type == "Retitle" ) { + return new BugCommandRetitle( bug, config->readEntry( type ), pkg ); + } else if ( type == "Severity" ) { + return new BugCommandSeverity( bug, config->readEntry( type ), pkg ); + } else if ( type == "Reply" ) { + return new BugCommandReply( bug, config->readEntry( type ), config->readNumEntry("Recipient",Normal) ); + } else if ( type == "ReplyPrivate" ) { + QStringList args = config->readListEntry( type ); + if ( args.count() != 2 ) return 0; + return new BugCommandReplyPrivate( bug, *(args.at(0)), *(args.at(1)) ); + } else { + kdDebug() << "Warning! Unknown bug command '" << type << "'" << endl; + return 0; + } +} + +///////////////////// Close ///////////////////// + +QString BugCommandClose::controlString() const +{ + if (m_message.isEmpty()) { + return "close " + m_bug.number(); + } else { + return QString::null; + } +} + +QString BugCommandClose::mailAddress() const +{ + kdDebug() << "BugCommandClose::mailAddress(): number: " << m_bug.number() << endl; + + if (m_message.isEmpty()) { + return QString::null; + } else { + return m_bug.number() + "-done@bugs.kde.org"; + } +} + +QString BugCommandClose::mailText() const +{ + if (m_message.isEmpty()) { + return QString::null; + } else { + return m_message; + } +} + +QString BugCommandClose::name() +{ + return i18n("Close"); +} + +QString BugCommandClose::details() const +{ + return m_message; +} + +void BugCommandClose::save( KConfig *config ) +{ + config->writeEntry( "Close",m_message ); +} + +///////////////////// Close Silently ///////////////////// + +QString BugCommandCloseSilently::controlString() const +{ + return "done " + m_bug.number(); +} + +QString BugCommandCloseSilently::name() +{ + return i18n("Close Silently"); +} + +void BugCommandCloseSilently::save( KConfig *config ) +{ + config->writeEntry( "CloseSilently", true ); +} + +///////////////////// Reopen ///////////////////// + +QString BugCommandReopen::controlString() const +{ + return "reopen " + m_bug.number(); +} + +QString BugCommandReopen::name() +{ + return i18n("Reopen"); +} + +void BugCommandReopen::save( KConfig *config ) +{ + config->writeEntry( "Reopen", true ); +} + +///////////////////// Retitle ///////////////////// + +QString BugCommandRetitle::controlString() const +{ + return "retitle " + m_bug.number() + " " + m_title; +} + +QString BugCommandRetitle::name() +{ + return i18n("Retitle"); +} + +QString BugCommandRetitle::details() const +{ + return m_title; +} + +void BugCommandRetitle::save( KConfig *config ) +{ + config->writeEntry( "Retitle", m_title ); +} + +///////////////////// Merge ///////////////////// + +QString BugCommandMerge::controlString() const +{ + return "merge " + m_bugNumbers.join(" "); +} + +QString BugCommandMerge::name() +{ + return i18n("Merge"); +} + +QString BugCommandMerge::details() const +{ + return m_bugNumbers.join(", "); +} + +void BugCommandMerge::save( KConfig *config ) +{ + config->writeEntry( "Merge", m_bugNumbers ); +} + +///////////////////// Unmerge ///////////////////// + +QString BugCommandUnmerge::controlString() const +{ + return "unmerge " + m_bug.number(); +} + +QString BugCommandUnmerge::name() +{ + return i18n("Unmerge"); +} + +void BugCommandUnmerge::save( KConfig *config ) +{ + config->writeEntry( "Unmerge", true ); +} + +///////////////////// Reply ///////////////////// + +QString BugCommandReply::mailAddress() const +{ + return m_bug.number() + "@bugs.kde.org"; +#if 0 + switch ( m_recipient ) { + case Normal: + return m_bug.number() + "@bugs.kde.org"; + case Maintonly: + return m_bug.number() + "-maintonly@bugs.kde.org"; + case Quiet: + return m_bug.number() + "-quiet@bugs.kde.org"; + } + return QString::null; +#endif +} + +QString BugCommandReply::mailText() const +{ + return m_message; +} + +QString BugCommandReply::name() +{ + return i18n("Reply"); +#if 0 + switch ( m_recipient ) { + case Normal: + return i18n("Reply"); + case Maintonly: + return i18n("Reply (Maintonly)"); + case Quiet: + return i18n("Reply (Quiet)"); + } + return QString::null; +#endif +} + +QString BugCommandReply::details() const +{ + return m_message; +} + +void BugCommandReply::save( KConfig *config ) +{ + config->writeEntry( "Reply", m_message ); +#if 0 + config->writeEntry( "Recipient", m_recipient ); +#endif +} + +///////////////////// Reply Private ///////////////////// + +QString BugCommandReplyPrivate::mailAddress() const +{ + return m_address; +} + +QString BugCommandReplyPrivate::mailText() const +{ + return m_message; +} + +QString BugCommandReplyPrivate::name() +{ + return i18n("Private Reply"); +} + +QString BugCommandReplyPrivate::details() const +{ + return m_message; +} + +void BugCommandReplyPrivate::save( KConfig *config ) +{ + QStringList args; + args << m_address; + args << m_message; + config->writeEntry( "ReplyPrivate", args ); +} + +///////////////////// Severity ///////////////////// + +QString BugCommandSeverity::controlString() const +{ + return "severity " + m_bug.number() + " " + m_severity.lower(); +} + +QString BugCommandSeverity::name() +{ + return i18n("Severity"); +} + +QString BugCommandSeverity::details() const +{ + return m_severity; +} + +void BugCommandSeverity::save( KConfig *config ) +{ + config->writeEntry( "Severity", m_severity ); +} + +///////////////////// Reassign ///////////////////// + +QString BugCommandReassign::controlString() const +{ + return "reassign " + m_bug.number() + " " + m_package; +} + +QString BugCommandReassign::name() +{ + return i18n("Reassign"); +} + +QString BugCommandReassign::details() const +{ + return m_package; +} + +void BugCommandReassign::save( KConfig *config ) +{ + config->writeEntry( "Reassign", m_package ); +} diff --git a/kbugbuster/backend/bugcommand.h b/kbugbuster/backend/bugcommand.h new file mode 100644 index 00000000..96b9c85c --- /dev/null +++ b/kbugbuster/backend/bugcommand.h @@ -0,0 +1,217 @@ +#ifndef BUGCOMMAND_H +#define BUGCOMMAND_H + +#include <qstring.h> +#include <qstringlist.h> + +#include "bug.h" +#include "package.h" + +class KConfig; + +class BugCommand { + public: + enum Mode { Normal, Maintonly, Quiet }; + enum State { None, Queued, Sent }; + + BugCommand( const Bug &bug ) : m_bug( bug ) {} + BugCommand( const Bug &bug, const Package &pkg ) : m_bug( bug ), m_package( pkg ) {} + virtual ~BugCommand() {} + + virtual QString controlString() const { return QString::null; } + + virtual QString mailAddress() const { return QString::null; } + virtual QString mailText() const { return QString::null; } + + Bug bug() const { return m_bug; } + Package package() const { return m_package; } + + virtual QString name(); + virtual QString details(); + + virtual QString type() const { return QString::null; } + + virtual void save( KConfig * ) = 0; + static BugCommand *load( KConfig *, const QString &type ); + + protected: + Bug m_bug; + Package m_package; +}; + +class BugCommandClose : public BugCommand { + public: + BugCommandClose( const Bug &bug, const QString &message, const Package &pkg ) : + BugCommand( bug, pkg ), m_message( message ) {} + + QString controlString() const; + QString mailAddress() const; + QString mailText() const; + + QString name(); + QString details() const; + + QString type() const { return QString::fromLatin1("Close"); } + + void save( KConfig * ); + + private: + QString m_message; +}; + +class BugCommandCloseSilently : public BugCommand { + public: + BugCommandCloseSilently( const Bug &bug, const Package &pkg ) : + BugCommand( bug, pkg ) {} + + QString controlString() const; + + QString name(); + + QString type() const { return QString::fromLatin1("CloseSilently"); } + + void save( KConfig * ); +}; + +class BugCommandReopen : public BugCommand { + public: + BugCommandReopen( const Bug &bug, const Package &pkg ) : + BugCommand( bug, pkg ) {} + + QString controlString() const; + + QString name(); + + QString type() const { return QString::fromLatin1("Reopen"); } + + void save( KConfig * ); +}; + +class BugCommandRetitle : public BugCommand { + public: + BugCommandRetitle( const Bug &bug, const QString &title, const Package &pkg ) : + BugCommand( bug, pkg ), m_title( title ) {} + + QString controlString() const; + + QString name(); + QString details() const; + + QString type() const { return QString::fromLatin1("Retitle"); } + + void save( KConfig * ); + + private: + QString m_title; +}; + +class BugCommandMerge : public BugCommand { + public: + BugCommandMerge( const QStringList &bugNumbers, const Package &pkg ) : + BugCommand( Bug(), pkg ), m_bugNumbers( bugNumbers ) {} + + QString controlString() const; + + QString name(); + QString details() const; + + QString type() const { return QString::fromLatin1("Merge"); } + + void save( KConfig * ); + + private: + QStringList m_bugNumbers; +}; + +class BugCommandUnmerge : public BugCommand { + public: + BugCommandUnmerge( const Bug &bug, const Package &pkg ) : + BugCommand( bug, pkg ) {} + + QString name(); + + QString type() const { return QString::fromLatin1("Unmerge"); } + + void save( KConfig * ); + + QString controlString() const; +}; + +class BugCommandReply : public BugCommand { + public: + BugCommandReply( const Bug &bug, const QString &message, const int &recipient) : + BugCommand( bug ), m_message( message ), m_recipient( recipient ) {} + + QString mailAddress() const; + QString mailText() const; + + QString name(); + QString details() const; + + QString type() const { return QString::fromLatin1("Reply"); } + + void save( KConfig * ); + + private: + QString m_message; + int m_recipient; +}; + +class BugCommandReplyPrivate : public BugCommand { + public: + BugCommandReplyPrivate( const Bug &bug, const QString &address, + const QString &message ) : + BugCommand( bug ), m_address( address ), m_message( message ) {} + + QString mailAddress() const; + QString mailText() const; + + QString name(); + QString details() const; + + QString type() const { return QString::fromLatin1("ReplyPrivate"); } + + void save( KConfig * ); + + private: + QString m_address; + QString m_message; +}; + +class BugCommandSeverity : public BugCommand { + public: + BugCommandSeverity( const Bug &bug, const QString &severity, const Package &pkg ) : + BugCommand( bug, pkg ), m_severity( severity ) {} + + QString name(); + QString details() const; + + QString type() const { return QString::fromLatin1("Severity"); } + + QString controlString() const; + + void save( KConfig * ); + + private: + QString m_severity; +}; + +class BugCommandReassign : public BugCommand { + public: + BugCommandReassign( const Bug &bug, const QString &package, const Package &pkg ) : + BugCommand( bug, pkg ), m_package( package ) {} + + QString name(); + QString details() const; + + QString type() const { return QString::fromLatin1("Reassign"); } + + QString controlString() const; + + void save( KConfig * ); + + private: + QString m_package; +}; + +#endif diff --git a/kbugbuster/backend/bugdetails.cpp b/kbugbuster/backend/bugdetails.cpp new file mode 100644 index 00000000..ec512d79 --- /dev/null +++ b/kbugbuster/backend/bugdetails.cpp @@ -0,0 +1,268 @@ + +#include "bugdetails.h" + +#include "bugdetailsimpl.h" +#include <qstringlist.h> +#include <kdebug.h> +#include <kmdcodec.h> +#include <kmessagebox.h> +#include <qregexp.h> + +BugDetails::BugDetails() +{ +} + +BugDetails::BugDetails( BugDetailsImpl *impl ) : + m_impl( impl ) +{ +} + +BugDetails::BugDetails( const BugDetails &other ) +{ + (*this) = other; +} + +BugDetails &BugDetails::operator=( const BugDetails &rhs ) +{ + m_impl = rhs.m_impl; + return *this; +} + +BugDetails::~BugDetails() +{ +} + +QString BugDetails::version() const +{ + if ( !m_impl ) + return QString::null; + + return m_impl->version; +} + +QString BugDetails::source() const +{ + if ( !m_impl ) + return QString::null; + + return m_impl->source; +} + +QString BugDetails::compiler() const +{ + if ( !m_impl ) + return QString::null; + + return m_impl->compiler; +} + +QString BugDetails::os() const +{ + if ( !m_impl ) + return QString::null; + + return m_impl->os; +} + +QDateTime BugDetails::submissionDate() const +{ + if ( !m_impl ) return QDateTime(); + + if ( m_impl->parts.count() > 0 ) { + return m_impl->parts.last().date; + } + return QDateTime(); +} + +int BugDetails::age() const +{ + if ( !m_impl ) + return 0; + + return submissionDate().daysTo( QDateTime::currentDateTime() ); +} + +BugDetailsPart::List BugDetails::parts() const +{ + if ( !m_impl ) + return BugDetailsPart::List(); + + return m_impl->parts; +} + +QValueList<BugDetailsImpl::AttachmentDetails> BugDetails::attachmentDetails() const +{ + if ( m_impl ) + return m_impl->attachments; + else + return QValueList<BugDetailsImpl::AttachmentDetails>(); +} + +void BugDetails::addAttachmentDetails( const QValueList<BugDetailsImpl::AttachmentDetails>& attch ) +{ + if ( m_impl ) + m_impl->attachments = attch; +} + +QValueList<BugDetails::Attachment> BugDetails::extractAttachments() const +{ + QValueList<BugDetails::Attachment> lst; + if ( !m_impl ) + return lst; + BugDetailsPart::List parts = m_impl->parts; + for ( BugDetailsPart::List::ConstIterator it = parts.begin(); it != parts.end(); ++it ) { + lst += extractAttachments( (*it).text ); + } + return lst; +} + +//#define DEBUG_EXTRACT + +QValueList<BugDetails::Attachment> BugDetails::extractAttachments( const QString& text ) +{ + QValueList<BugDetails::Attachment> lst; + QStringList lines = QStringList::split( '\n', text ); +#ifdef DEBUG_EXTRACT + kdDebug() << k_funcinfo << lines.count() << " lines." << endl; +#endif + QString boundary; + for ( QStringList::Iterator it = lines.begin() ; it != lines.end() ; ++it ) + { +#ifdef DEBUG_EXTRACT + kdDebug() << "Line: " << *it << endl; +#endif + if ( (*it).startsWith( " Content-Type" ) ) // ## leading space comes from khtml + { +#ifdef DEBUG_EXTRACT + //kdDebug() << "BugDetails::extractAttachments going back, looking for empty or boundary=" << boundary << endl; +#endif + // Rewind until last empty line + QStringList::Iterator rit = it; + for ( ; rit != lines.begin() ; --rit ) { + QString line = *rit; + if ( line.endsWith( "<br />" ) ) + line = line.left( line.length() - 6 ); + while ( !line.isEmpty() && line[0] == ' ' ) + line.remove( 0, 1 ); +#ifdef DEBUG_EXTRACT + kdDebug() << "Back: '" << line << "'" << endl; +#endif + if ( line.isEmpty() ) { + ++rit; // boundary is next line + boundary = *rit; + if ( boundary.endsWith( "<br />" ) ) + boundary = boundary.left( boundary.length() - 6 ); + if ( boundary[0] == ' ' ) + boundary.remove( 0, 1 ); + kdDebug() << "BugDetails::extractAttachments boundary=" << boundary << endl; + break; + } + if ( line == boundary ) + break; + } + // Forward until next empty line (end of headers) - and parse filename + QString filename; + QString encoding; + rit = it; + for ( ; rit != lines.end() ; ++rit ) { + QString header = *rit; + if ( header.endsWith( "<br />" ) ) + header = header.left( header.length() - 6 ); + if ( header[0] == ' ' ) + header.remove( 0, 1 ); +#ifdef DEBUG_EXTRACT + kdDebug() << "Header: '" << header << "'" << endl; +#endif + if ( header.isEmpty() ) + break; + if ( header.startsWith( "Content-Disposition:" ) ) + { +#ifdef DEBUG_EXTRACT + kdDebug() << "BugDetails::extractAttachments found header " << *rit << endl; +#endif + // Taken from libkdenetwork/kmime_headers.cpp + int pos=header.find("filename=", 0, false); + QString fn; + if(pos>-1) { + pos+=9; + fn=header.mid(pos, header.length()-pos); + if ( fn.startsWith( "\"" ) && fn.endsWith( "\"" ) ) + fn = fn.mid( 1, fn.length() - 2 ); + //filename=decodeRFC2047String(fn, &e_ncCS, defaultCS(), forceCS()); + filename = fn; // hack + kdDebug() << "BugDetails::extractAttachments filename=" << filename << endl; + } + + } + else if ( header.startsWith( "Content-Transfer-Encoding:" ) ) + { + encoding = header.mid( 26 ).lower(); + while ( encoding[0] == ' ' ) + encoding.remove( 0, 1 ); + kdDebug() << "BugDetails::extractAttachments encoding=" << encoding << endl; + } + } //for + if ( rit == lines.end() ) + break; + + it = rit; + if ( rit != lines.end() && !filename.isEmpty() ) + { + ++it; + if ( it == lines.end() ) + break; + // Read encoded contents + QString contents; + for ( ; it != lines.end() ; ++it ) + { + QString line = *it; + if ( line.endsWith( "</tt>" ) ) + line = line.left( line.length() - 5 ); + if ( line.endsWith( "<br />" ) ) // necessary for the boundary check + line = line.left( line.length() - 6 ); + while ( !line.isEmpty() && line[0] == ' ' ) + line.remove( 0, 1 ); + if ( line.isEmpty() || line == boundary ) // end of attachment + break; + if ( line == boundary+"--" ) // end of last attachment + { + boundary = QString::null; + break; + } + contents += line; // no newline, because of linebreaking between <br and /> + } + contents = contents.replace( QRegExp("<br */>"), QString::null ); +#ifdef DEBUG_EXTRACT + kdDebug() << "BugDetails::extractAttachments contents=***\n" << contents << "\n***" << endl; +#endif + kdDebug() << "BugDetails::extractAttachments creating attachment " << filename << endl; + Attachment a; + if ( encoding == "base64" ) + KCodecs::base64Decode( contents.local8Bit(), a.contents /*out*/ ); + else + //KCodecs::uudecode( contents.local8Bit(), a.contents /*out*/ ); + KMessageBox::information( 0, i18n("Attachment %1 could not be decoded.\nEncoding: %2").arg(filename).arg(encoding) ); +#ifdef DEBUG_EXTRACT + kdDebug() << "Result: ***\n" << QCString( a.contents.data(), a.contents.size()+1 ) << "\n*+*" << endl; +#endif + a.filename = filename; + lst.append(a); + if ( it == lines.end() ) + break; + } + } + } +#ifdef DEBUG_EXTRACT + kdDebug() << "BugDetails::extractAttachments returning " << lst.count() << " attachments for this part." << endl; +#endif + return lst; +} + +bool BugDetails::operator==( const BugDetails &rhs ) +{ + return m_impl == rhs.m_impl; +} + +/** + * vim:ts=4:sw=4:et + */ diff --git a/kbugbuster/backend/bugdetails.h b/kbugbuster/backend/bugdetails.h new file mode 100644 index 00000000..08d20777 --- /dev/null +++ b/kbugbuster/backend/bugdetails.h @@ -0,0 +1,55 @@ +#ifndef __bugdetails_h__ +#define __bugdetails_h__ + +#include "person.h" +#include "bugdetailspart.h" +#include "bugdetailsimpl.h" + +#include <qvaluelist.h> + +#include <ksharedptr.h> + +class BugDetailsImpl; + +class BugDetails +{ +public: + typedef QValueList<BugDetails> List; + struct Attachment { + QByteArray contents; + QString filename; + }; + + BugDetails(); + BugDetails( BugDetailsImpl *impl ); + BugDetails( const BugDetails &other ); + BugDetails &operator=( const BugDetails &rhs ); + ~BugDetails(); + + QString version() const; + QString source() const; + QString compiler() const; + QString os() const; + BugDetailsPart::List parts() const; + void addAttachmentDetails( const QValueList<BugDetailsImpl::AttachmentDetails>& attch ); + QValueList<BugDetailsImpl::AttachmentDetails> attachmentDetails() const; + QValueList<BugDetails::Attachment> extractAttachments() const; + static QValueList<BugDetails::Attachment> extractAttachments( const QString& text ); + + QDateTime submissionDate() const; + int age() const; + + bool operator==( const BugDetails &rhs ); + + bool isNull() const { return m_impl == 0; } + +private: + BugDetailsImpl *impl() const { return m_impl; } + + KSharedPtr<BugDetailsImpl> m_impl; +}; + +#endif + +/* vim: set sw=4 ts=4 et softtabstop=4: */ + diff --git a/kbugbuster/backend/bugdetailsimpl.h b/kbugbuster/backend/bugdetailsimpl.h new file mode 100644 index 00000000..df3e5d6d --- /dev/null +++ b/kbugbuster/backend/bugdetailsimpl.h @@ -0,0 +1,40 @@ +#ifndef __bugdetails_impl_h__ +#define __bugdetails_impl_h__ + +#include <ksharedptr.h> + +#include "bugdetailspart.h" + +struct BugDetailsImpl : public KShared +{ +public: + BugDetailsImpl( const QString &_version, const QString &_source, + const QString &_compiler, const QString &_os, + const BugDetailsPart::List &_parts ) + : version( _version ), source( _source ), compiler( _compiler ), + os( _os ), parts( _parts ) {} + + struct AttachmentDetails { + AttachmentDetails() { } + AttachmentDetails( const QString& descr, const QString& dt, + const QString& idf ) : description( descr ), + date( dt ), + id( idf ) { } + QString description; + QString date; + QString id; + }; + + QString version; + QString source; + QString compiler; + QString os; + BugDetailsPart::List parts; + QValueList<BugDetailsImpl::AttachmentDetails> attachments; +}; + +#endif + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/bugdetailsjob.cpp b/kbugbuster/backend/bugdetailsjob.cpp new file mode 100644 index 00000000..83599c1d --- /dev/null +++ b/kbugbuster/backend/bugdetailsjob.cpp @@ -0,0 +1,50 @@ + +#include "bugdetailsjob.h" +#include "bug.h" +#include "bugdetails.h" +#include "bugdetailsimpl.h" +#include "packageimpl.h" +#include "bugserver.h" +#include "processor.h" + +#include <kdebug.h> +#include <assert.h> + +BugDetailsJob::BugDetailsJob( BugServer *server ) + : BugJob( server ) +{ +} + +BugDetailsJob::~BugDetailsJob() +{ +} + +void BugDetailsJob::start( const Bug &bug ) +{ + m_bug = bug; + + KURL bugUrl = server()->bugDetailsUrl( bug ); + + kdDebug() << "BugDetailsJob::start(): " << bugUrl.url() << endl; + BugJob::start( bugUrl ); +} + +void BugDetailsJob::process( const QByteArray &data ) +{ + BugDetails bugDetails; + + KBB::Error err = server()->processor()->parseBugDetails( data, bugDetails ); + + if ( err ) { + emit error( i18n("Bug %1: %2").arg( m_bug.number() ) + .arg( err.message() ) ); + } else { + emit bugDetailsAvailable( m_bug, bugDetails ); + } +} + +#include "bugdetailsjob.moc" + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/bugdetailsjob.h b/kbugbuster/backend/bugdetailsjob.h new file mode 100644 index 00000000..e07ad3a2 --- /dev/null +++ b/kbugbuster/backend/bugdetailsjob.h @@ -0,0 +1,32 @@ +#ifndef __bugdetailsjob_h__ +#define __bugdetailsjob_h__ + +#include "bugjob.h" +#include "bug.h" +#include "bugdetails.h" +#include "bugdetailspart.h" + +class BugDetailsJob : public BugJob +{ + Q_OBJECT + public: + BugDetailsJob( BugServer * ); + virtual ~BugDetailsJob(); + + void start( const Bug &bug ); + + signals: + void bugDetailsAvailable( const Bug &bug, const BugDetails &details ); + + protected: + virtual void process( const QByteArray &data ); + + private: + Bug m_bug; +}; + +#endif + +/* + * vim:ts=4:sw=4:et + */ diff --git a/kbugbuster/backend/bugdetailspart.h b/kbugbuster/backend/bugdetailspart.h new file mode 100644 index 00000000..483057c8 --- /dev/null +++ b/kbugbuster/backend/bugdetailspart.h @@ -0,0 +1,21 @@ +#ifndef BUGDETAILSPART_H +#define BUGDETAILSPART_H + +#include <qvaluelist.h> +#include <qdatetime.h> + +struct BugDetailsPart +{ + typedef QValueList<BugDetailsPart> List; + + BugDetailsPart () {} + BugDetailsPart( const Person &_sender, const QDateTime &_date, + const QString &_text ) + : sender( _sender ), date( _date ), text( _text ) {} + + Person sender; + QDateTime date; + QString text; +}; + +#endif diff --git a/kbugbuster/backend/bugimpl.h b/kbugbuster/backend/bugimpl.h new file mode 100644 index 00000000..630105a1 --- /dev/null +++ b/kbugbuster/backend/bugimpl.h @@ -0,0 +1,36 @@ +#ifndef __bug_impl_h__ +#define __bug_impl_h__ + +#include "person.h" +#include "bug.h" + +#include <kurl.h> +#include <ksharedptr.h> + +struct BugImpl : public KShared +{ +public: + BugImpl( const QString &_title, const Person &_submitter, QString _number, + uint _age, Bug::Severity _severity, Person _developerTODO, + Bug::Status _status, const Bug::BugMergeList& _mergedWith ) + : age( _age ), title( _title ), submitter( _submitter ), number( _number ), + severity( _severity ), developerTODO( _developerTODO ), + status( _status ), mergedWith( _mergedWith ) + { + } + + uint age; + QString title; + Person submitter; + QString number; + Bug::Severity severity; + Person developerTODO; + Bug::Status status; + + Bug::BugMergeList mergedWith; +}; + +#endif + +// vim: set sw=4 ts=4 sts=4 et: + diff --git a/kbugbuster/backend/bugjob.cpp b/kbugbuster/backend/bugjob.cpp new file mode 100644 index 00000000..6f32fb97 --- /dev/null +++ b/kbugbuster/backend/bugjob.cpp @@ -0,0 +1,97 @@ + +#include "bugjob.h" + +#include "kbbprefs.h" + +#include <kio/job.h> + +#include <string.h> +#include <klocale.h> +#include <kdebug.h> + +BugJob::BugJob( BugServer *server ) + : Job( false ), mServer( server ) +{ +} + +BugJob::~BugJob() +{ +} + +void BugJob::start( const KURL &url ) +{ + kdDebug() << "BugJob::start(): " << url.url() << endl; + + if ( KBBPrefs::instance()->mDebugMode ) { + BugSystem::saveQuery( url ); + } + + // ### obey post, if necessary + + KIO::Job *job = KIO::get( url, true /*always 'reload=true', we have our own cache*/, false ); + + connect( job, SIGNAL( result( KIO::Job * ) ), + this, SLOT( ioResult( KIO::Job * ) ) ); + connect( job, SIGNAL( data( KIO::Job *, const QByteArray & ) ), + this, SLOT( ioData( KIO::Job *, const QByteArray & ) ) ); + connect( job, SIGNAL( infoMessage( KIO::Job *, const QString & ) ), + this, SLOT( ioInfoMessage( KIO::Job *, const QString & ) ) ); + connect( job, SIGNAL( percent( KIO::Job *, unsigned long ) ), + this, SLOT( ioInfoPercent( KIO::Job *, unsigned long ) ) ); +} + +void BugJob::ioResult( KIO::Job *job ) +{ + m_error = job->error(); + m_errorText = job->errorText(); + + if ( job->error() ) + { + emit error( m_errorText ); + BugSystem::self()->unregisterJob(this); + this->kill(); + return; + } + + infoMessage( i18n( "Parsing..." ) ); + +#if 0 + kdDebug() << "--START:" << m_data << ":END--" << endl; +#endif + + if ( KBBPrefs::instance()->mDebugMode ) { + BugSystem::saveResponse( m_data ); + } + + process( m_data ); + infoMessage( i18n( "Ready." ) ); + + emit jobEnded( this ); + + delete this; +} + +void BugJob::ioData( KIO::Job *, const QByteArray &data ) +{ + unsigned int start = m_data.size(); + + m_data.resize( m_data.size() + data.size() ); + memcpy( m_data.data() + start, data.data(), data.size() ); +} + +void BugJob::ioInfoMessage( KIO::Job *, const QString &_text ) +{ + QString text = _text; + emit infoMessage( text ); +} + +void BugJob::ioInfoPercent( KIO::Job *, unsigned long percent ) +{ + emit infoPercent( percent ); +} + +#include "bugjob.moc" + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/bugjob.h b/kbugbuster/backend/bugjob.h new file mode 100644 index 00000000..2c439810 --- /dev/null +++ b/kbugbuster/backend/bugjob.h @@ -0,0 +1,45 @@ +#ifndef KBB_BUGJOB_H +#define KBB_BUGJOB_H + +#include <kio/jobclasses.h> + +#include "bugserver.h" + +class BugJob : public KIO::Job +{ + Q_OBJECT + public: + BugJob( BugServer * ); + virtual ~BugJob(); + + BugServer *server() const { return mServer; } + + signals: + void infoMessage( const QString &text ); + void infoPercent( unsigned long percent ); + void error( const QString &text ); + void jobEnded( BugJob * ); + + protected: + void start( const KURL &url /*, const KParts::URLArgs &args = KParts::URLArgs()*/ ); + + virtual void process( const QByteArray &data ) = 0; + + private slots: + void ioResult( KIO::Job *job ); + + void ioData( KIO::Job *job, const QByteArray &data ); + + void ioInfoMessage( KIO::Job *job, const QString &text ); + + void ioInfoPercent( KIO::Job *job, unsigned long percent ); + + private: + QByteArray m_data; + BugServer *mServer; +}; + +#endif +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/buglistjob.cpp b/kbugbuster/backend/buglistjob.cpp new file mode 100644 index 00000000..e1dea2f4 --- /dev/null +++ b/kbugbuster/backend/buglistjob.cpp @@ -0,0 +1,75 @@ +/* + This file is part of KBugBuster. + + Copyright (c) 2002,2003 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ + +#include "buglistjob.h" +#include "bug.h" +#include "bugimpl.h" +#include "packageimpl.h" +#include "bugserver.h" +#include "domprocessor.h" +#include "htmlparser.h" + +#include <kdebug.h> + +#include <qbuffer.h> +#include <qregexp.h> + +BugListJob::BugListJob( BugServer *server ) + : BugJob( server ) +{ +} + +BugListJob::~BugListJob() +{ +} + +void BugListJob::start( const Package &pkg, const QString &component ) +{ + m_package = pkg; + m_component = component; + + BugJob::start( server()->bugListUrl( pkg, component ) ); +} + + +void BugListJob::process( const QByteArray &data ) +{ + Bug::List bugs; + + KBB::Error err = server()->processor()->parseBugList( data, bugs ); + + if ( err ) { + emit error( i18n("Package %1: %2").arg( m_package.name() ) + .arg( err.message() ) ); + } else { + emit bugListAvailable( m_package, m_component, bugs ); + } +} + + +#include "buglistjob.moc" + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/buglistjob.h b/kbugbuster/backend/buglistjob.h new file mode 100644 index 00000000..0260d3a0 --- /dev/null +++ b/kbugbuster/backend/buglistjob.h @@ -0,0 +1,58 @@ +/* + This file is part of KBugBuster. + + Copyright (c) 2002,2003 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ +#ifndef BUGLISTJOB_H +#define BUGLISTJOB_H + +#include "bugjob.h" +#include "package.h" +#include "bug.h" + +#include <qdom.h> + +class BugListJob : public BugJob +{ + Q_OBJECT + public: + BugListJob( BugServer * ); + virtual ~BugListJob(); + + void start( const Package &pkg, const QString &component ); + + protected: + void process( const QByteArray &data ); + + signals: + void bugListAvailable( const Package &pkg, const QString &component, const Bug::List &bugs ); + + protected: + Package m_package; + QString m_component; +}; + + +#endif + +/* + * vim:ts=4:sw=4:et + */ diff --git a/kbugbuster/backend/bugmybugsjob.cpp b/kbugbuster/backend/bugmybugsjob.cpp new file mode 100644 index 00000000..92aa4448 --- /dev/null +++ b/kbugbuster/backend/bugmybugsjob.cpp @@ -0,0 +1,78 @@ +/* + This file is part of KBugBuster. + + Copyright (c) 2002-2003 Cornelius Schumacher <schumacher@kde.org> + Copyright (c) 2004 Martijn Klingens <klingens@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ + +#include "bugmybugsjob.h" +#include "bug.h" +#include "bugimpl.h" +#include "packageimpl.h" +#include "bugserver.h" +#include "domprocessor.h" +#include "htmlparser.h" + +#include <kdebug.h> + +#include <qbuffer.h> +#include <qregexp.h> + +BugMyBugsJob::BugMyBugsJob( BugServer *server ) +: BugJob( server ) +{ +} + +BugMyBugsJob::~BugMyBugsJob() +{ +} + +void BugMyBugsJob::start() +{ + KURL url = server()->serverConfig().baseUrl(); + url.setFileName( "buglist.cgi" ); + url.setQuery( "bug_status=NEW&bug_status=ASSIGNED&bug_status=UNCONFIRMED&bug_status=REOPENED" ); + url.addQueryItem( "email1", server()->serverConfig().user() ); + url.addQueryItem( "emailtype1", "exact" ); + url.addQueryItem( "emailassigned_to1", "1" ); + url.addQueryItem( "emailreporter1", "1" ); + url.addQueryItem( "format", "rdf" ); + BugJob::start( url ); +} + +void BugMyBugsJob::process( const QByteArray &data ) +{ + Bug::List bugs; + + Processor *processor = new RdfProcessor( server() ); + KBB::Error err = processor->parseBugList( data, bugs ); + delete processor; + + if ( err ) + emit error( i18n( "My Bugs: %2" ).arg( err.message() ) ); + else + emit bugListAvailable( i18n( "My Bugs" ), bugs ); +} + +#include "bugmybugsjob.moc" + +// vim: set sw=4 ts=4 et: + diff --git a/kbugbuster/backend/bugmybugsjob.h b/kbugbuster/backend/bugmybugsjob.h new file mode 100644 index 00000000..f51ee229 --- /dev/null +++ b/kbugbuster/backend/bugmybugsjob.h @@ -0,0 +1,52 @@ +/* + This file is part of KBugBuster. + + Copyright (c) 2002-2003 Cornelius Schumacher <schumacher@kde.org> + Copyright (c) 2004 Martijn Klingens <klingens@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ + +#ifndef BUGMYBUGSJOB_H +#define BUGMYBUGSJOB_H + +#include "bugjob.h" +#include "bug.h" + +class BugMyBugsJob : public BugJob +{ + Q_OBJECT + +public: + BugMyBugsJob( BugServer * ); + virtual ~BugMyBugsJob(); + + void start(); + +protected: + void process( const QByteArray &data ); + +signals: + void bugListAvailable( const QString &label, const Bug::List &bugs ); +}; + +#endif + +// vim: set ts=4 sw=4 et: + diff --git a/kbugbuster/backend/bugserver.cpp b/kbugbuster/backend/bugserver.cpp new file mode 100644 index 00000000..0e6a70e6 --- /dev/null +++ b/kbugbuster/backend/bugserver.cpp @@ -0,0 +1,412 @@ +/* + This file is part of KBugBuster. + Copyright (c) 2002,2003 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ + +#include "bugserver.h" +#include "kbbprefs.h" +#include "rdfprocessor.h" +#include "bugcache.h" +#include "bugcommand.h" +#include "mailsender.h" +#include "bugserverconfig.h" +#include "htmlparser.h" + +#include <kstandarddirs.h> +#include <ksimpleconfig.h> +#include <kdebug.h> + +BugServer::BugServer() +{ + init(); +} + +BugServer::BugServer( const BugServerConfig &cfg ) + : mServerConfig( cfg ) +{ + init(); +} + +void BugServer::init() +{ + mCache = new BugCache( identifier() ); + + QString commandsFile = locateLocal( "appdata", identifier() + "commands" ); + mCommandsFile = new KSimpleConfig( commandsFile ); + + QString bugzilla = mServerConfig.bugzillaVersion(); + + if ( bugzilla == "KDE" ) mProcessor = new DomProcessor( this ); + else if ( bugzilla == "2.10" ) mProcessor = new HtmlParser_2_10( this ); + else if ( bugzilla == "2.14.2" ) mProcessor = new HtmlParser_2_14_2( this ); + else if ( bugzilla == "2.17.1" ) mProcessor = new HtmlParser_2_17_1( this ); + else mProcessor = new HtmlParser( this ); + + loadCommands(); +} + +BugServer::~BugServer() +{ + saveCommands(); + + delete mProcessor; + delete mCommandsFile; + delete mCache; +} + +void BugServer::setServerConfig( const BugServerConfig &cfg ) +{ + mServerConfig = cfg; +} + +BugServerConfig &BugServer::serverConfig() +{ + return mServerConfig; +} + +QString BugServer::identifier() +{ + QString id = mServerConfig.baseUrl().host(); + return id; +} + +Processor *BugServer::processor() const +{ + return mProcessor; +} + +KURL BugServer::packageListUrl() +{ + KURL url = mServerConfig.baseUrl(); + + mProcessor->setPackageListQuery( url ); + + return url; +} + +KURL BugServer::bugListUrl( const Package &product, const QString &component ) +{ + KURL url = mServerConfig.baseUrl(); + + mProcessor->setBugListQuery( url, product, component ); + + return url; +} + +KURL BugServer::bugDetailsUrl( const Bug &bug ) +{ + KURL url = mServerConfig.baseUrl(); + + mProcessor->setBugDetailsQuery( url, bug ); + + return url; +} + +KURL BugServer::bugLink( const Bug &bug ) +{ + KURL url = mServerConfig.baseUrl(); + + url.setFileName( "show_bug.cgi" ); + url.setQuery( "id=" + bug.number() ); + + kdDebug() << "URL: " << url.url() << endl; + + return url; +} + +KURL BugServer::attachmentViewLink( const QString &id ) +{ + KURL url = mServerConfig.baseUrl(); + + url.setFileName( "attachment.cgi" ); + url.setQuery( "id=" + id + "&action=view" ); + + return url; +} + +KURL BugServer::attachmentEditLink( const QString &id ) +{ + KURL url = mServerConfig.baseUrl(); + + url.setFileName( "attachment.cgi" ); + url.setQuery( "id=" + id + "&action=edit" ); + + return url; +} + +Bug::Status BugServer::bugStatus( const QString &str ) +{ + if ( str == "UNCONFIRMED" ) { + return Bug::Unconfirmed; + } else if ( str == "NEW" ) { + return Bug::New; + } else if ( str == "ASSIGNED" ) { + return Bug::Assigned; + } else if ( str == "REOPENED" ) { + return Bug::Reopened; + } else if ( str == "RESOLVED" ) { + return Bug::Closed; + } else if ( str == "VERIFIED" ) { + return Bug::Closed; + } else if ( str == "CLOSED" ) { + return Bug::Closed; + } else { + return Bug::StatusUndefined; + } +} + +Bug::Severity BugServer::bugSeverity( const QString &str ) +{ + if ( str == "critical" ) { + return Bug::Critical; + } else if ( str == "grave" ) { + return Bug::Grave; + } else if ( str == "major" ) { + return Bug::Major; + } else if ( str == "crash" ) { + return Bug::Crash; + } else if ( str == "normal" ) { + return Bug::Normal; + } else if ( str == "minor" ) { + return Bug::Minor; + } else if ( str == "wishlist" ) { + return Bug::Wishlist; + } else { + return Bug::SeverityUndefined; + } +} + +void BugServer::readConfig( KConfig * /*config*/ ) +{ +} + +void BugServer::writeConfig( KConfig * /*config*/ ) +{ +} + +bool BugServer::queueCommand( BugCommand *cmd ) +{ + // mCommands[bug] is a QPtrList. Get or create, set to autodelete, then append command. + mCommands[cmd->bug().number()].setAutoDelete( true ); + QPtrListIterator<BugCommand> cmdIt( mCommands[cmd->bug().number()] ); + for ( ; cmdIt.current(); ++cmdIt ) + if ( cmdIt.current()->type() == cmd->type() ) + return false; + mCommands[cmd->bug().number()].append( cmd ); + return true; +} + +QPtrList<BugCommand> BugServer::queryCommands( const Bug &bug ) const +{ + CommandsMap::ConstIterator it = mCommands.find( bug.number() ); + if (it == mCommands.end()) return QPtrList<BugCommand>(); + else return *it; +} + +bool BugServer::hasCommandsFor( const Bug &bug ) const +{ + CommandsMap::ConstIterator it = mCommands.find( bug.number() ); + return it != mCommands.end(); +} + +void BugServer::sendCommands( MailSender *mailer, const QString &senderName, + const QString &senderEmail, bool sendBCC, + const QString &recipient ) +{ + // Disable mail commands for non-KDE servers + if ( mServerConfig.baseUrl() != KURL( "http://bugs.kde.org" ) ) return; + + QString controlText; + + // For each bug that has commands..... + CommandsMap::ConstIterator it; + for(it = mCommands.begin(); it != mCommands.end(); ++it ) { + Bug bug; + Package pkg; + // And for each command.... + QPtrListIterator<BugCommand> cmdIt( *it ); + for ( ; cmdIt.current() ; ++cmdIt ) { + BugCommand* cmd = cmdIt.current(); + bug = cmd->bug(); + if (!cmd->package().isNull()) + pkg = cmd->package(); + if (!cmd->controlString().isNull()) { + kdDebug() << "control@bugs.kde.org: " << cmd->controlString() << endl; + controlText += cmd->controlString() + "\n"; + } else { + kdDebug() << cmd->mailAddress() << ": " << cmd->mailText() << endl; + // ### hm, should probably re-use the existing mailer instance and + // implement message queueing for smtp + MailSender *directMailer = mailer->clone(); +#if 0 + connect( directMailer, SIGNAL( status( const QString & ) ), + this, SIGNAL( infoMessage( const QString & ) ) ); +#endif + if (!directMailer->send( senderName, senderEmail, cmd->mailAddress(), + cmd->bug().title().prepend( "Re: " ), + cmd->mailText(), sendBCC, recipient )) { + delete mailer; + return; + } + } + } + if (!bug.isNull()) { + mCommandsFile->deleteGroup( bug.number(), true ); // done, remove command + mCache->invalidateBugDetails( bug ); + if ( !pkg.isNull() ) { + mCache->invalidateBugList( pkg, QString::null ); // the status of the bug comes from the buglist... + + QStringList::ConstIterator it2; + for (it2 = pkg.components().begin();it2 != pkg.components().end();++it2) { + mCache->invalidateBugList( pkg, (*it2) ); // the status of the bug comes from the buglist... + } + } + } + } + + if (!controlText.isEmpty()) { + kdDebug() << "control@bugs.kde.org doesn't work anymore" << endl; +#if 0 + if ( !mailer->send( senderName, senderEmail, "control@bugs.kde.org", + i18n("Mail generated by KBugBuster"), controlText, + sendBCC, recipient )) + return; +#endif + } else { + delete mailer; + } + + mCommands.clear(); +} + +void BugServer::clearCommands( const QString &bug ) +{ + mCommands.remove( bug ); + mCommandsFile->deleteGroup( bug, true ); +} + +bool BugServer::commandsPending() const +{ + if ( mCommands.count() > 0 ) return true; + else return false; +} + +QStringList BugServer::listCommands() const +{ + QStringList result; + CommandsMap::ConstIterator it; + for(it = mCommands.begin(); it != mCommands.end(); ++it ) { + QPtrListIterator<BugCommand> cmdIt( *it ); + for ( ; cmdIt.current() ; ++cmdIt ) { + BugCommand* cmd = cmdIt.current(); + if (!cmd->controlString().isNull()) + result.append( i18n("Control command: %1").arg(cmd->controlString()) ); + else + result.append( i18n("Mail to %1").arg(cmd->mailAddress()) ); + } + } + return result; +} + +QStringList BugServer::bugsWithCommands() const +{ + QStringList bugs; + + CommandsMap::ConstIterator it; + for(it = mCommands.begin(); it != mCommands.end(); ++it ) { + bugs.append( it.key() ); + } + + return bugs; +} + +void BugServer::saveCommands() const +{ + CommandsMap::ConstIterator it; + for(it = mCommands.begin(); it != mCommands.end(); ++it ) { + mCommandsFile->setGroup( it.key() ); + QPtrListIterator<BugCommand> cmdIt( *it ); + for ( ; cmdIt.current() ; ++cmdIt ) { + BugCommand* cmd = cmdIt.current(); + cmd->save( mCommandsFile ); + } + } + + mCommandsFile->sync(); +} + +void BugServer::loadCommands() +{ + mCommands.clear(); + + QStringList bugs = mCommandsFile->groupList(); + QStringList::ConstIterator it; + for( it = bugs.begin(); it != bugs.end(); ++it ) { + mCommandsFile->setGroup( *it ); + QMap<QString, QString> entries = mCommandsFile->entryMap ( *it ); + QMap<QString, QString>::ConstIterator it; + for( it = entries.begin(); it != entries.end(); ++it ) { + QString type = it.key(); + BugCommand *cmd = BugCommand::load( mCommandsFile, type ); + if ( cmd ) { + mCommands[cmd->bug().number()].setAutoDelete(true); + mCommands[cmd->bug().number()].append(cmd); + } + } + } +} + +void BugServer::setPackages( const Package::List &packages ) +{ + mPackages = packages; +} + +const Package::List &BugServer::packages() const +{ + return mPackages; +} + +void BugServer::setBugs( const Package &pkg, const QString &component, + const Bug::List &bugs ) +{ + QPair<Package, QString> pkg_key = QPair<Package, QString>(pkg, component); + mBugs[ pkg_key ] = bugs; +} + +const Bug::List &BugServer::bugs( const Package &pkg, const QString &component ) +{ + QPair<Package, QString> pkg_key = QPair<Package, QString>(pkg, component); + return mBugs[ pkg_key ]; +} + +void BugServer::setBugDetails( const Bug &bug, const BugDetails &details ) +{ + mBugDetails[ bug ] = details; +} + +const BugDetails &BugServer::bugDetails( const Bug &bug ) +{ + return mBugDetails[ bug ]; +} + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/bugserver.h b/kbugbuster/backend/bugserver.h new file mode 100644 index 00000000..3b534fa0 --- /dev/null +++ b/kbugbuster/backend/bugserver.h @@ -0,0 +1,152 @@ +/* + This file is part of KBugBuster. + Copyright (c) 2002,2003 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ +#ifndef BUGSERVER_H +#define BUGSERVER_H + +#include <qstring.h> + +#include <kurl.h> + +#include "bug.h" +#include "package.h" +#include "bugsystem.h" +#include "bugserverconfig.h" + +class Processor; +class BugCache; +class MailSender; +class BugServerConfig; + +class BugServer +{ + public: + BugServer(); + BugServer( const BugServerConfig & ); + ~BugServer(); + + /** + BugServer takes ownership of the BugServerConfig object. + */ + void setServerConfig( const BugServerConfig & ); + BugServerConfig &serverConfig(); + + QString identifier(); + + BugCache *cache() const { return mCache; } + + KURL packageListUrl(); + + KURL bugListUrl( const Package &, const QString &component ); + + KURL bugDetailsUrl( const Bug & ); + + KURL bugLink( const Bug & ); + KURL attachmentViewLink( const QString &id ); + KURL attachmentEditLink( const QString &id ); + + Bug::Status bugStatus( const QString & ); + + Bug::Severity bugSeverity( const QString & ); + + Processor *processor() const; + + void readConfig( KConfig * ); + + void writeConfig( KConfig * ); + + /** + Queue a new command. + */ + bool queueCommand( BugCommand * ); + /** + Return all the commands for a given bug. + */ + QPtrList<BugCommand> queryCommands( const Bug & ) const; + /** + Return true if we have a least one command for this bug. + */ + bool hasCommandsFor( const Bug &bug ) const; + /** + Send all commands (generate the mails). + */ + void sendCommands( MailSender *, const QString &senderName, + const QString &senderEmail, bool sendBCC, + const QString &recipient ); + /** + Forget all commands for a given bug. + */ + void clearCommands( const QString &bug ); + /** + Return true if any command has been created. + */ + bool commandsPending() const; + /** + List all pending commands. + */ + QStringList listCommands() const; + /** + Return numbers of all bugs having at least one command queued. + */ + QStringList bugsWithCommands() const; + + void saveCommands() const; + void loadCommands(); + + void setPackages( const Package::List & ); + const Package::List &packages() const; + + void setBugs( const Package &, const QString &component, + const Bug::List & ); + const Bug::List &bugs( const Package &, const QString &component ); + + void setBugDetails( const Bug &, const BugDetails & ); + const BugDetails &bugDetails( const Bug & ); + + private: + void init(); + + BugServerConfig mServerConfig; + + Processor *mProcessor; + + BugCache *mCache; + + Package::List mPackages; + // Map package -> list of bugs + typedef QMap< QPair<Package, QString>, Bug::List > BugListMap; + BugListMap mBugs; + // Map bug -> bug details (i.e. contents of the report) + typedef QMap< Bug, BugDetails > BugDetailsMap; + BugDetailsMap mBugDetails; + // Map bug-number -> list of commands + typedef QMap< QString, QPtrList<BugCommand> > CommandsMap; + CommandsMap mCommands; + + KSimpleConfig *mCommandsFile; +}; + +#endif + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/bugserverconfig.cpp b/kbugbuster/backend/bugserverconfig.cpp new file mode 100644 index 00000000..0669c0ab --- /dev/null +++ b/kbugbuster/backend/bugserverconfig.cpp @@ -0,0 +1,150 @@ +/* + This file is part of KBugBuster. + + Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ + +#include "bugserverconfig.h" + +#include "kbbprefs.h" + +#include <kdebug.h> +#include <kconfig.h> + +BugServerConfig::BugServerConfig() +{ + mName = "KDE"; + mBaseUrl = "http://bugs.kde.org"; + mUser = "bugzilla@kde.org"; + mBugzillaVersion = "KDE"; +} + +BugServerConfig::BugServerConfig( const QString &name, const KURL &baseUrl ) + : mName( name ), mBaseUrl( baseUrl ), mBugzillaVersion( "KDE" ) +{ +} + +BugServerConfig::~BugServerConfig() +{ +} + +void BugServerConfig::setName( const QString &name ) +{ + mName = name; +} + +QString BugServerConfig::name() const +{ + return mName; +} + +void BugServerConfig::setBaseUrl( const KURL &baseUrl ) +{ + mBaseUrl = baseUrl; +} + +KURL BugServerConfig::baseUrl() const +{ + return mBaseUrl; +} + +void BugServerConfig::setUser( const QString &user ) +{ + mUser = user; +} + +QString BugServerConfig::user() const +{ + return mUser; +} + +void BugServerConfig::setPassword( const QString &password ) +{ + mPassword = password; +} + +QString BugServerConfig::password() const +{ + return mPassword; +} + +void BugServerConfig::setBugzillaVersion( const QString &s ) +{ + mBugzillaVersion = s; +} + +QString BugServerConfig::bugzillaVersion() const +{ + return mBugzillaVersion; +} + +QStringList BugServerConfig::bugzillaVersions() +{ + QStringList v; + + v << "2.10"; + v << "2.14.2"; + v << "2.16.2"; + v << "2.17.1"; + v << "KDE"; + v << "Bugworld"; + + return v; +} + +void BugServerConfig::readConfig( KConfig *cfg, const QString &name ) +{ + mName = name; + + cfg->setGroup( "BugServer " + name ); + + mBaseUrl = cfg->readEntry( "BaseUrl" ); + mUser = cfg->readEntry( "User" ); + mPassword = cfg->readEntry( "Password" ); + + mBugzillaVersion = cfg->readEntry( "BugzillaVersion", "KDE" ); + + mRecentPackages = cfg->readListEntry( "RecentPackages" ); + mCurrentPackage = cfg->readEntry( "CurrentPackage" ); + mCurrentComponent = cfg->readEntry( "CurrentComponent" ); + mCurrentBug = cfg->readEntry( "CurrentBug" ); +} + +void BugServerConfig::writeConfig( KConfig *cfg ) +{ + cfg->setGroup( "BugServer " + mName ); + + cfg->writeEntry( "BaseUrl", mBaseUrl.url() ); + cfg->writeEntry( "User", mUser ); + cfg->writeEntry( "Password", mPassword ); + + cfg->writeEntry( "BugzillaVersion", mBugzillaVersion ); + + cfg->writeEntry( "RecentPackages", mRecentPackages ); + cfg->writeEntry( "CurrentPackage", mCurrentPackage ); + cfg->writeEntry( "CurrentComponent", mCurrentComponent ); + cfg->writeEntry( "CurrentBug", mCurrentBug ); +} + + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/bugserverconfig.h b/kbugbuster/backend/bugserverconfig.h new file mode 100644 index 00000000..2c9be828 --- /dev/null +++ b/kbugbuster/backend/bugserverconfig.h @@ -0,0 +1,91 @@ +/* + This file is part of KBugBuster. + + Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ +#ifndef BUGSERVERCONFIG_H +#define BUGSERVERCONFIG_H + +#include <qstring.h> +#include <qstringlist.h> + +#include <kurl.h> + +class KConfig; + +class BugServerConfig +{ + public: + BugServerConfig(); + BugServerConfig( const QString &name, const KURL &baseUrl ); + ~BugServerConfig(); + + void setName( const QString &name ); + QString name() const; + + void setBaseUrl( const KURL &url ); + KURL baseUrl() const; + + void setUser( const QString &user ); + QString user() const; + + void setPassword( const QString &password ); + QString password() const; + + void readConfig( KConfig *, const QString &name ); + void writeConfig( KConfig * ); + + static QStringList bugzillaVersions(); + + void setBugzillaVersion( const QString & ); + QString bugzillaVersion() const; + + void setRecentPackages( const QStringList &v ) { mRecentPackages = v; } + QStringList recentPackages() const { return mRecentPackages; } + + void setCurrentPackage( const QString &v ) { mCurrentPackage = v; } + QString currentPackage() const { return mCurrentPackage; } + + void setCurrentComponent( const QString &v ) { mCurrentComponent = v; } + QString currentComponent() const { return mCurrentComponent; } + + void setCurrentBug( const QString &v ) { mCurrentBug = v; } + QString currentBug() const { return mCurrentBug; } + + private: + QString mName; + KURL mBaseUrl; + QString mUser; + QString mPassword; + + QString mBugzillaVersion; + + QStringList mRecentPackages; + QString mCurrentPackage; + QString mCurrentComponent; + QString mCurrentBug; +}; + +#endif + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/bugsystem.cpp b/kbugbuster/backend/bugsystem.cpp new file mode 100644 index 00000000..26432e08 --- /dev/null +++ b/kbugbuster/backend/bugsystem.cpp @@ -0,0 +1,436 @@ + +#include "bugsystem.h" +#include "packagelistjob.h" +#include "buglistjob.h" +#include "bugmybugsjob.h" +#include "bugdetailsjob.h" +#include "bugcommand.h" + +#include <kstaticdeleter.h> +#include <kdebug.h> +#include <klocale.h> +#include <kemailsettings.h> +#include <kstandarddirs.h> +#include <ksimpleconfig.h> +#include <kconfig.h> + +#include "packageimpl.h" +#include "bugimpl.h" +#include "bugdetailsimpl.h" +#include "mailsender.h" +#include "kbbprefs.h" +#include "bugserver.h" +#include "bugserverconfig.h" +#include "bugcache.h" + +KStaticDeleter<BugSystem> bssd; + +BugSystem *BugSystem::s_self = 0; + +QString BugSystem::mLastResponse; + +BugSystem *BugSystem::self() +{ + if ( !s_self ) + s_self = bssd.setObject( s_self, new BugSystem ); + + return s_self; +} + +BugSystem::BugSystem() + : m_disconnected( false ) +{ + mServer = 0; +} + +BugSystem::~BugSystem() +{ + QValueList<BugServer *>::ConstIterator it; + for( it = mServerList.begin(); it != mServerList.end(); ++it ) { + delete *it; + } +} + +BugCache *BugSystem::cache()const +{ + return mServer->cache(); +} + +void BugSystem::setDisconnected( bool disconnected ) +{ + m_disconnected = disconnected; +} + +bool BugSystem::disconnected() const +{ + return m_disconnected; +} + +void BugSystem::retrievePackageList() +{ + mServer->setPackages( mServer->cache()->loadPackageList() ); + + if( !mServer->packages().isEmpty() ) { + emit packageListAvailable( mServer->packages() ); + } else { + emit packageListCacheMiss(); + + if ( !m_disconnected ) + { + emit packageListLoading(); + + PackageListJob *job = new PackageListJob( mServer ); + connect( job, SIGNAL( packageListAvailable( const Package::List & ) ), + this, SIGNAL( packageListAvailable( const Package::List & ) ) ); + connect( job, SIGNAL( packageListAvailable( const Package::List & ) ), + this, SLOT( setPackageList( const Package::List & ) ) ); + connect( job, SIGNAL( error( const QString & ) ), + this, SIGNAL( loadingError( const QString & ) ) ); + connectJob( job ); + + registerJob( job ); + + job->start(); + } + } +} + +void BugSystem::retrieveBugList( const Package &pkg, const QString &component ) +{ + kdDebug() << "BugSystem::retrieveBugList(): " << pkg.name() << endl; + + if ( pkg.isNull() ) + return; + + mServer->setBugs( pkg, component, + mServer->cache()->loadBugList( pkg, component, + m_disconnected ) ); + + // Since the GUI stops showing the splash widget after this signal, + // we should not emit anything on a cache miss... + if( !mServer->bugs( pkg, component ).isEmpty() ) + emit bugListAvailable( pkg, component, mServer->bugs( pkg, component ) ); + else + { + emit bugListCacheMiss( pkg ); + + if ( !m_disconnected ) + { + kdDebug() << "BugSystem::retrieveBugList() starting job" << endl; + emit bugListLoading( pkg, component ); + + BugListJob *job = new BugListJob( mServer ); + connect( job, SIGNAL( bugListAvailable( const Package &, const QString &, const Bug::List & ) ), + this, SIGNAL( bugListAvailable( const Package &, const QString &, const Bug::List & ) ) ); + connect( job, SIGNAL( bugListAvailable( const Package &, const QString &, const Bug::List & ) ), + this, SLOT( setBugList( const Package &, const QString &, const Bug::List & ) ) ); + connect( job, SIGNAL( error( const QString & ) ), + this, SIGNAL( loadingError( const QString & ) ) ); + connectJob( job ); + + registerJob( job ); + + job->start( pkg, component ); + } + } +} + +void BugSystem::retrieveMyBugsList() +{ + kdDebug() << k_funcinfo << endl; + + if ( m_disconnected ) + { + // This function is not cached for now + emit bugListCacheMiss( i18n( "My Bugs" ) ); + } + else + { + kdDebug() << k_funcinfo << "Starting job" << endl; + + emit bugListLoading( i18n( "Retrieving My Bugs list..." ) ); + + BugMyBugsJob *job = new BugMyBugsJob( mServer ); + + connect( job, SIGNAL( bugListAvailable( const QString &, const Bug::List & ) ), + this, SIGNAL( bugListAvailable( const QString &, const Bug::List & ) ) ); + connect( job, SIGNAL( error( const QString & ) ), + this, SIGNAL( loadingError( const QString & ) ) ); + connectJob( job ); + + registerJob( job ); + + job->start(); + } +} + +void BugSystem::retrieveBugDetails( const Bug &bug ) +{ + if ( bug.isNull() ) + return; + + kdDebug() << "BugSystem::retrieveBugDetails(): " << bug.number() << endl; + + mServer->setBugDetails( bug, mServer->cache()->loadBugDetails( bug ) ); + + if ( !mServer->bugDetails( bug ).isNull() ) { +// kdDebug() << "Found in cache." << endl; + emit bugDetailsAvailable( bug, mServer->bugDetails( bug ) ); + } else { +// kdDebug() << "Not found in cache." << endl; + emit bugDetailsCacheMiss( bug ); + + if ( !m_disconnected ) { + emit bugDetailsLoading( bug ); + + BugDetailsJob *job = new BugDetailsJob( mServer ); + connect( job, SIGNAL( bugDetailsAvailable( const Bug &, const BugDetails & ) ), + this, SIGNAL( bugDetailsAvailable( const Bug &, const BugDetails & ) ) ); + connect( job, SIGNAL( bugDetailsAvailable( const Bug &, const BugDetails & ) ), + this, SLOT( setBugDetails( const Bug &, const BugDetails & ) ) ); + connect( job, SIGNAL( error( const QString & ) ), + this, SIGNAL( bugDetailsLoadingError() ) ); + connectJob( job ); + + registerJob( job ); + + job->start( bug ); + } + } +} + +void BugSystem::connectJob( BugJob *job ) +{ + connect( job, SIGNAL( infoMessage( const QString & ) ), + this, SIGNAL( infoMessage( const QString & ) ) ); + connect( job, SIGNAL( infoPercent( unsigned long ) ), + this, SIGNAL( infoPercent( unsigned long ) ) ); + connect( job, SIGNAL( jobEnded( BugJob * ) ), + SLOT( unregisterJob( BugJob * ) ) ); +} + +void BugSystem::setPackageList( const Package::List &pkgs ) +{ + mServer->setPackages( pkgs ); + + mServer->cache()->savePackageList( pkgs ); +} + +void BugSystem::setBugList( const Package &pkg, const QString &component, const Bug::List &bugs ) +{ + mServer->setBugs( pkg, component, bugs ); + mServer->cache()->saveBugList( pkg, component, bugs ); +} + +void BugSystem::setBugDetails( const Bug &bug, const BugDetails &details ) +{ + mServer->setBugDetails( bug , details ); + + mServer->cache()->saveBugDetails( bug, details ); +} + +Package::List BugSystem::packageList() const +{ + return mServer->packages(); +} + +Package BugSystem::package( const QString &pkgname ) const +{ + Package::List::ConstIterator it; + for( it = mServer->packages().begin(); it != mServer->packages().end(); ++it ) { + if( pkgname == (*it).name() ) return (*it); + } + return Package(); +} + +Bug BugSystem::bug( const Package &pkg, const QString &component, const QString &number ) const +{ + Bug::List bugs = mServer->bugs( pkg, component ); + + Bug::List::ConstIterator it; + for( it = bugs.begin(); it != bugs.end(); ++it ) { + if( number == (*it).number() ) return (*it); + } + return Bug(); +} + +void BugSystem::queueCommand( BugCommand *cmd ) +{ + if ( mServer->queueCommand( cmd ) ) emit commandQueued( cmd ); +} + +void BugSystem::clearCommands( const QString &bug ) +{ + mServer->clearCommands( bug ); + + emit commandCanceled( bug ); +} + +void BugSystem::clearCommands() +{ + QStringList bugs = mServer->bugsWithCommands(); + + QStringList::ConstIterator it; + for( it = bugs.begin(); it != bugs.end(); ++it ) { + clearCommands( *it ); + } +} + +void BugSystem::sendCommands() +{ + QString recipient = KBBPrefs::instance()->mOverrideRecipient; + bool sendBCC = KBBPrefs::instance()->mSendBCC; + + KEMailSettings emailSettings; + QString senderName = emailSettings.getSetting( KEMailSettings::RealName ); + QString senderEmail = emailSettings.getSetting( KEMailSettings::EmailAddress ); + QString smtpServer = emailSettings.getSetting( KEMailSettings::OutServer ); + + MailSender::MailClient client = (MailSender::MailClient)KBBPrefs::instance()->mMailClient; + + // ### connect to signals + MailSender *mailer = new MailSender( client, smtpServer ); + connect( mailer, SIGNAL( status( const QString & ) ), + SIGNAL( infoMessage( const QString & ) ) ); + + mServer->sendCommands( mailer, senderName, senderEmail, sendBCC, recipient ); +} + +void BugSystem::setServerList( const QValueList<BugServerConfig> &servers ) +{ + if ( servers.isEmpty() ) return; + + QString currentServer; + if ( mServer ) currentServer = mServer->serverConfig().name(); + else currentServer = KBBPrefs::instance()->mCurrentServer; + + killAllJobs(); + + QValueList<BugServer *>::ConstIterator serverIt; + for( serverIt = mServerList.begin(); serverIt != mServerList.end(); + ++serverIt ) { + delete *serverIt; + } + mServerList.clear(); + + QValueList<BugServerConfig>::ConstIterator cfgIt; + for( cfgIt = servers.begin(); cfgIt != servers.end(); ++cfgIt ) { + mServerList.append( new BugServer( *cfgIt ) ); + } + + setCurrentServer( currentServer ); +} + +QValueList<BugServer *> BugSystem::serverList() +{ + return mServerList; +} + +void BugSystem::setCurrentServer( const QString &name ) +{ + killAllJobs(); + + BugServer *server = findServer( name ); + if ( server ) { + mServer = server; + } else { + kdError() << "Server '" << name << "' not known." << endl; + if ( mServerList.isEmpty() ) { + kdError() << "Fatal error: server list empty." << endl; + } else { + mServer = mServerList.first(); + } + } + + if ( mServer ) { + KBBPrefs::instance()->mCurrentServer = mServer->serverConfig().name(); + } +} + +BugServer *BugSystem::findServer( const QString &name ) +{ + QValueList<BugServer *>::ConstIterator serverIt; + for( serverIt = mServerList.begin(); serverIt != mServerList.end(); + ++serverIt ) { + if ( (*serverIt)->serverConfig().name() == name ) return *serverIt; + } + return 0; +} + +void BugSystem::saveQuery( const KURL &url ) +{ + mLastResponse = "Query: " + url.url(); + mLastResponse += "\n\n"; +} + +void BugSystem::saveResponse( const QByteArray &response ) +{ + mLastResponse += response; +} + +QString BugSystem::lastResponse() +{ + return mLastResponse; +} + +void BugSystem::readConfig( KConfig *config ) +{ + config->setGroup("Servers"); + QStringList servers = config->readListEntry( "Servers" ); + + QValueList<BugServerConfig> serverList; + + if ( servers.isEmpty() ) { + serverList.append( BugServerConfig() ); + } else { + QStringList::ConstIterator it; + for( it = servers.begin(); it != servers.end(); ++it ) { + BugServerConfig cfg; + cfg.readConfig( config, *it ); + serverList.append( cfg ); + } + } + + setServerList( serverList ); +} + +void BugSystem::writeConfig( KConfig *config ) +{ + QValueList<BugServer *>::ConstIterator itServer; + QStringList servers; + QValueList<BugServer *> serverList = BugSystem::self()->serverList(); + for( itServer = serverList.begin(); itServer != serverList.end(); + ++itServer ) { + BugServerConfig serverConfig = (*itServer)->serverConfig(); + servers.append( serverConfig.name() ); + serverConfig.writeConfig( config ); + } + + config->setGroup("Servers"); + config->writeEntry( "Servers", servers ); +} + +void BugSystem::registerJob( BugJob *job ) +{ + mJobs.append( job ); +} + +void BugSystem::unregisterJob( BugJob *job ) +{ + mJobs.removeRef( job ); +} + +void BugSystem::killAllJobs() +{ + BugJob *job; + for( job = mJobs.first(); job; job = mJobs.next() ) { + job->kill(); + unregisterJob( job ); + } +} + +#include "bugsystem.moc" + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/bugsystem.h b/kbugbuster/backend/bugsystem.h new file mode 100644 index 00000000..c573698b --- /dev/null +++ b/kbugbuster/backend/bugsystem.h @@ -0,0 +1,146 @@ +#ifndef __bugsystem_h__ +#define __bugsystem_h__ + +#include "package.h" +#include "bug.h" +#include "bugdetails.h" +#include "bugcache.h" + +#include <kurl.h> + +#include <qobject.h> +#include <qptrlist.h> +#include <qmap.h> +#include <qpair.h> + +class KConfig; + +class BugCommand; +class BugServer; +class BugServerConfig; +class BugJob; + +class BugSystem : public QObject +{ + Q_OBJECT + friend class BugJob; + public: + BugSystem(); + virtual ~BugSystem(); + + static BugSystem *self(); + + BugCache *cache()const; + BugServer *server() const { return mServer; } + + /** + BugSystem takes ownership of the BugServerConfig objects. + */ + void setServerList( const QValueList<BugServerConfig> &servers ); + QValueList<BugServer *> serverList(); + + void setCurrentServer( const QString & ); + + void retrievePackageList(); + void retrieveBugList( const Package &, const QString &component ); + void retrieveBugDetails( const Bug & ); + + /** + * Load the bugs the user reported himself, or for which he is the assigned to person + */ + void retrieveMyBugsList(); + + /** + Queue a new command. + */ + void queueCommand( BugCommand * ); + /** + Forget all commands for a given bug. + */ + void clearCommands( const QString &bug ); + /** + Forget all commands for all bugs. + */ + void clearCommands(); + /** + Send all commands (generate the mails). + */ + void sendCommands(); + + void setDisconnected( bool ); + bool disconnected() const; + + Package::List packageList() const; + + Package package( const QString &pkgname ) const; + Bug bug( const Package &pkg, const QString &component, const QString &number ) const; + + static void saveQuery( const KURL &url ); + static void saveResponse( const QByteArray &d ); + static QString lastResponse(); + + void readConfig( KConfig * ); + void writeConfig( KConfig * ); + + signals: + void packageListAvailable( const Package::List &pkgs ); + void bugListAvailable( const Package &pkg, const QString &component, const Bug::List & ); + void bugListAvailable( const QString &label, const Bug::List & ); + void bugDetailsAvailable( const Bug &, const BugDetails & ); + + void packageListLoading(); + void bugListLoading( const Package &, const QString &component ); + void bugListLoading( const QString &label ); + void bugDetailsLoading( const Bug & ); + + void packageListCacheMiss(); + void bugListCacheMiss( const Package &package ); + void bugListCacheMiss( const QString &label ); + void bugDetailsCacheMiss( const Bug & ); + + void bugDetailsLoadingError(); + + void infoMessage( const QString &message ); + void infoPercent( unsigned long percent ); + + void commandQueued( BugCommand * ); + void commandCanceled( const QString & ); + + void loadingError( const QString &text ); + + protected: + BugServer *findServer( const QString &name ); + + void registerJob( BugJob * ); + + void connectJob( BugJob * ); + + void killAllJobs(); + + protected slots: + void unregisterJob( BugJob * ); + + private slots: + void setPackageList( const Package::List &pkgs ); + void setBugList( const Package &pkg, const QString &component, const Bug::List &bugs ); + void setBugDetails( const Bug &bug, const BugDetails &details ); + + private: + bool m_disconnected; + + BugServer *mServer; + + QValueList<BugServer *> mServerList; + + QPtrList<BugJob> mJobs; + + static BugSystem *s_self; + + static QString mLastResponse; +}; + +#endif + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/domprocessor.cpp b/kbugbuster/backend/domprocessor.cpp new file mode 100644 index 00000000..7643ca11 --- /dev/null +++ b/kbugbuster/backend/domprocessor.cpp @@ -0,0 +1,407 @@ +/* + This file is part of KBugBuster. + Copyright (c) 2002 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ + +#include "domprocessor.h" + +#include <qregexp.h> +#include <qstylesheet.h> + +#include <kdebug.h> +#include <kmdcodec.h> + +#include "bugserver.h" +#include "packageimpl.h" +#include "bugimpl.h" +#include "bugdetailsimpl.h" +#include "kbbprefs.h" + +DomProcessor::DomProcessor( BugServer *server ) + : Processor( server ) +{ +} + +DomProcessor::~DomProcessor() +{ +} + +KBB::Error DomProcessor::parsePackageList( const QByteArray &data, + Package::List &packages ) +{ + QDomDocument doc; + if ( !doc.setContent( data ) ) { + return KBB::Error( "Error parsing xml response for package list request." ); + } + + QDomElement bugzilla = doc.documentElement(); + + if ( bugzilla.isNull() ) { + return KBB::Error( "No document in xml response." ); + } + + KBB::Error err = parseDomPackageList( bugzilla, packages ); + + return err; +} + +KBB::Error DomProcessor::parseBugList( const QByteArray &data, Bug::List &bugs ) +{ + QDomDocument doc; + if ( !doc.setContent( data ) ) { + return KBB::Error( "Error parsing xml response for bug list request" ); + } + + QDomElement bugzilla = doc.documentElement(); + + if ( bugzilla.isNull() ) { + return KBB::Error( "No document in xml response." ); + } + + KBB::Error err = parseDomBugList( bugzilla, bugs ); + + return err; +} + +KBB::Error DomProcessor::parseBugDetails( const QByteArray &data, + BugDetails &bugDetails ) +{ + QDomDocument doc; + if ( !doc.setContent( data ) ) { + return KBB::Error( "Error parsing xml response for bug details request." ); + } + + QDomElement bugzilla = doc.documentElement(); + + if ( bugzilla.isNull() ) { + return KBB::Error( "No document in xml response." ); + } + + QDomNode p; + for ( p = bugzilla.firstChild(); !p.isNull(); p = p.nextSibling() ) { + QDomElement bug = p.toElement(); + if ( bug.tagName() != "bug" ) continue; + + KBB::Error err = parseDomBugDetails( bug, bugDetails ); + + if ( err ) return err; + } + + return KBB::Error(); +} + + +KBB::Error DomProcessor::parseDomPackageList( const QDomElement &element, + Package::List &packages ) +{ + QDomNode p; + for ( p = element.firstChild(); !p.isNull(); p = p.nextSibling() ) { + QDomElement bug = p.toElement(); + + if ( bug.tagName() != "product" ) continue; + + QString pkgName = bug.attribute( "name" ); + uint bugCount = 999; + Person maintainer; + QString description; + QStringList components; + + QDomNode n; + for( n = bug.firstChild(); !n.isNull(); n = n.nextSibling() ) { + QDomElement e = n.toElement(); + if ( e.tagName() == "descr" ) description= e.text().stripWhiteSpace(); + if ( e.tagName() == "component" ) components += e.text().stripWhiteSpace(); + } + + Package pkg( new PackageImpl( pkgName, description, bugCount, maintainer, components ) ); + + if ( !pkg.isNull() ) { + packages.append( pkg ); + } + } + + return KBB::Error(); +} + +KBB::Error DomProcessor::parseDomBugList( const QDomElement &topElement, + Bug::List &bugs ) +{ + QDomElement element; + + if ( topElement.tagName() != "querybugids" ) { + QDomNode buglist = topElement.namedItem( "querybugids" ); + element = buglist.toElement(); + if ( element.isNull() ) { + return KBB::Error( "No querybugids element found." ); + } + } else { + element = topElement; + } + + QDomNode p; + for ( p = element.firstChild(); !p.isNull(); p = p.nextSibling() ) { + QDomElement hit = p.toElement(); + + kdDebug() << "DomProcessor::parseDomBugList(): tag: " << hit.tagName() << endl; + + if ( hit.tagName() == "error" ) { + return KBB::Error( "Error: " + hit.text() ); + } else if ( hit.tagName() != "hit" ) continue; + + QString title; + QString submitterName; + QString submitterEmail; + QString bugNr; + Bug::Status status = Bug::StatusUndefined; + Bug::Severity severity = Bug::SeverityUndefined; + Person developerTodo; + Bug::BugMergeList mergedList; + uint age = 0xFFFFFFFF; + + QDomNode n; + for ( n = hit.firstChild(); !n.isNull(); n = n.nextSibling() ) + { + QDomElement e = n.toElement(); + + if ( e.tagName() == "bugid" ) + bugNr = e.text(); + else if ( e.tagName() == "status" ) + status = server()->bugStatus( e.text() ); + else if ( e.tagName() == "descr" ) + title = e.text(); + else if ( e.tagName() == "reporter" ) + submitterEmail = e.text(); + else if ( e.tagName() == "reporterName" ) + submitterName = e.text(); + else if ( e.tagName() == "severity" ) + severity = Bug::stringToSeverity( e.text() ); + else if ( e.tagName() == "creationdate" ) + age = ( QDateTime::fromString( e.text(), Qt::ISODate ) ).daysTo( QDateTime::currentDateTime() ); + } + + Person submitter( submitterName, submitterEmail ); + + Bug bug( new BugImpl( title, submitter, bugNr, age, severity, + developerTodo, status, mergedList ) ); + + if ( !bug.isNull() ) { + bugs.append( bug ); + } + } + + return KBB::Error(); +} + +KBB::Error DomProcessor::parseDomBugDetails( const QDomElement &element, + BugDetails &bugDetails ) +{ + if ( element.tagName() != "bug" ) return KBB::Error( "No <bug> tag found" ); + + BugDetailsPart::List parts; + QValueList<BugDetailsImpl::AttachmentDetails> attachments; + + QString versionXml; + QString osXml; + + QString version; + QString source; + QString compiler; + QString os; + + QDomNode n; + for( n = element.firstChild(); !n.isNull(); n = n.nextSibling() ) { + QDomElement e = n.toElement(); + if ( e.tagName() == "version" ) versionXml = e.text().stripWhiteSpace(); + if ( e.tagName() == "op_sys" ) osXml = e.text().stripWhiteSpace(); + + if ( e.tagName() == "long_desc" ) { + + QString encoding = e.attribute( "encoding" ); + + Person sender; + QDateTime date; + QString text; + + QDomNode n2; + for( n2 = e.firstChild(); !n2.isNull(); n2 = n2.nextSibling() ) { + QDomElement e2 = n2.toElement(); + if ( e2.tagName() == "who" ) { + sender = Person::parseFromString( e2.text() ); + } else if ( e2.tagName() == "bug_when" ) { + date = parseDate( e2.text().stripWhiteSpace() ); + } else if ( e2.tagName() == "thetext" ) { + QString in; + if ( encoding == "base64" ) { + in = KCodecs::base64Decode( e2.text().latin1() ); + } else { + in = e2.text(); + } + + QString raw = QStyleSheet::escape( in ); + + if ( parts.isEmpty() ) + { + QTextStream ts( &raw, IO_ReadOnly ); + QString line; + while( !( line = ts.readLine() ).isNull() ) { + if ( parseAttributeLine( line, "Version", version ) ) continue; + if ( parseAttributeLine( line, "Installed from", source ) ) continue; + if ( parseAttributeLine( line, "Compiler", compiler ) ) continue; + if ( parseAttributeLine( line, "OS", os ) ) continue; + + text += line + "\n"; + } + } else { + text += raw; + } + QString bugBaseURL = server()->serverConfig().baseUrl().htmlURL(); + text = "<pre>" + wrapLines( text ).replace( QRegExp( "(Created an attachment \\(id=([0-9]+)\\))" ), + "<a href=\"" + bugBaseURL + "/attachment.cgi?id=\\2&action=view\">\\1</a>" ) + "\n</pre>"; + } + } + + parts.prepend( BugDetailsPart( sender, date, text ) ); + } + + if ( e.tagName() == "attachment" ) { + QString attachid, date, desc; + for( QDomNode node = e.firstChild(); !node.isNull(); node = node.nextSibling() ) { + QDomElement e2 = node.toElement(); + if ( e2.tagName() == "attachid" ) { + attachid = e2.text(); + } else if ( e2.tagName() == "date" ) { + date = e2.text().stripWhiteSpace(); + } else if ( e2.tagName() == "desc" ) { + desc = "<pre>" + wrapLines( QStyleSheet::escape(e2.text()) ) + "\n</pre>"; + } + } + attachments.append( BugDetailsImpl::AttachmentDetails( desc, date, attachid ) ); + } + } + + if ( version.isEmpty() ) version = versionXml; + if ( os.isEmpty() ) os = osXml; + + bugDetails = BugDetails( new BugDetailsImpl( version, source, compiler, os, + parts ) ); + bugDetails.addAttachmentDetails( attachments ); + + return KBB::Error(); +} + +void DomProcessor::setPackageListQuery( KURL &url ) +{ + url.setFileName( "xml.cgi" ); + url.setQuery( "?data=versiontable" ); +} + +void DomProcessor::setBugListQuery( KURL &url, const Package &product, const QString &component ) +{ + if ( server()->serverConfig().bugzillaVersion() == "Bugworld" ) { + url.setFileName( "bugworld.cgi" ); + } else { + url.setFileName( "xmlquery.cgi" ); + } + + QString user = server()->serverConfig().user(); + + if ( component.isEmpty() ) + url.setQuery( "?user=" + user + "&product=" + product.name() ); + else + url.setQuery( "?user=" + user + "&product=" + product.name() + "&component=" + component ); + + if ( KBBPrefs::instance()->mShowClosedBugs ) + url.addQueryItem( "addClosed", "1" ); +} + +void DomProcessor::setBugDetailsQuery( KURL &url, const Bug &bug ) +{ + url.setFileName( "xml.cgi" ); + url.setQuery( "?id=" + bug.number() ); +} + +QString DomProcessor::wrapLines( const QString &text ) +{ + int wrap = KBBPrefs::instance()->mWrapColumn; + + QStringList lines = QStringList::split( '\n', text, true ); + //kdDebug() << lines.count() << " lines." << endl; + + QString out; + bool removeBlankLines = true; + for ( QStringList::Iterator it = lines.begin() ; it != lines.end() ; ++it ) + { + QString line = *it; + + if ( removeBlankLines ) { + if ( line.isEmpty() ) continue; + else removeBlankLines = false; + } + + //kdDebug() << "BugDetailsJob::processNode IN line='" << line << "'" << endl; + + QString wrappedLine; + while ( line.length() > uint( wrap ) ) + { + int breakPoint = line.findRev( ' ', wrap ); + //kdDebug() << "Breaking at " << breakPoint << endl; + if( breakPoint == -1 ) { + wrappedLine += line.left( wrap ) + '\n'; + line = line.mid( wrap ); + } else { + wrappedLine += line.left( breakPoint ) + '\n'; + line = line.mid( breakPoint + 1 ); + } + } + wrappedLine += line; // the remainder + //kdDebug() << "BugDetailsJob::processNode OUT wrappedLine='" << wrappedLine << "'" << endl; + + out += wrappedLine + "\n"; + } + + return out; +} + +bool DomProcessor::parseAttributeLine( const QString &line, const QString &key, + QString &result ) +{ + if ( !result.isEmpty() ) return false; + + if ( !line.startsWith( key + ":" ) ) return false; + + QString value = line.mid( key.length() + 1 ); + value = value.stripWhiteSpace(); + + result = value; + + return true; +} + +QDateTime DomProcessor::parseDate( const QString &dateStr ) +{ + QDateTime date = QDateTime::fromString( dateStr, Qt::ISODate ); + + return date; +} + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/domprocessor.h b/kbugbuster/backend/domprocessor.h new file mode 100644 index 00000000..1553ae4a --- /dev/null +++ b/kbugbuster/backend/domprocessor.h @@ -0,0 +1,69 @@ +/* + This file is part of KBugBuster. + Copyright (c) 2002 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ +#ifndef DOMPROCESSOR_H +#define DOMPROCESSOR_H + +#include "bug.h" +#include "bugdetails.h" +#include "package.h" +#include "error.h" +#include "processor.h" + +#include <kurl.h> + +#include <qdom.h> + +class BugServer; + +class DomProcessor : public Processor +{ + public: + DomProcessor( BugServer * ); + virtual ~DomProcessor(); + + KBB::Error parsePackageList( const QByteArray &data, + Package::List &packages ); + KBB::Error parseBugList( const QByteArray &data, Bug::List &bugs ); + KBB::Error parseBugDetails( const QByteArray &, BugDetails & ); + + void setPackageListQuery( KURL & ); + void setBugListQuery( KURL &, const Package &, const QString &component ); + void setBugDetailsQuery( KURL &, const Bug & ); + + protected: + virtual KBB::Error parseDomPackageList( const QDomElement &, + Package::List & ); + virtual KBB::Error parseDomBugList( const QDomElement &, Bug::List & ); + virtual KBB::Error parseDomBugDetails( const QDomElement &, BugDetails & ); + + QString wrapLines( const QString & ); + bool parseAttributeLine( const QString &line, const QString &key, + QString &result ); + QDateTime parseDate( const QString & ); +}; + +#endif + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/error.h b/kbugbuster/backend/error.h new file mode 100644 index 00000000..e12bcf61 --- /dev/null +++ b/kbugbuster/backend/error.h @@ -0,0 +1,43 @@ +/* + This file is part of KBugBuster. + Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ +#ifndef KBB_ERROR_H +#define KBB_ERROR_H + +namespace KBB { + +class Error +{ + public: + Error( const QString &msg = QString::null ) : mMsg( msg ) {} + + operator bool() { return !mMsg.isEmpty(); } + + QString message() const { return mMsg; } + + private: + QString mMsg; +}; + +} + +#endif diff --git a/kbugbuster/backend/htmlparser.cpp b/kbugbuster/backend/htmlparser.cpp new file mode 100644 index 00000000..7e53c1bd --- /dev/null +++ b/kbugbuster/backend/htmlparser.cpp @@ -0,0 +1,294 @@ +/* + This file is part of KBugBuster. + + Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ + +#include "htmlparser.h" +#include "bugimpl.h" +#include "packageimpl.h" + +#include <kdebug.h> + +#include <qbuffer.h> +#include <qregexp.h> +#include <qtextstream.h> + +KBB::Error HtmlParser::parseBugList( const QByteArray &data, Bug::List &bugs ) +{ + QBuffer buffer( data ); + if ( !buffer.open( IO_ReadOnly ) ) { + return KBB::Error( "Can't open buffer" ); + } + + QTextStream ts( &buffer ); + + mState = Idle; + + QString line; + while ( !( line = ts.readLine() ).isNull() ) { + KBB::Error err = parseLine( line, bugs ); + if ( err ) return err; + } + + return KBB::Error(); +} + +KBB::Error HtmlParser::parsePackageList( const QByteArray &data, + Package::List &packages ) +{ + init(); + + QBuffer buffer( data ); + if ( !buffer.open( IO_ReadOnly ) ) { + return KBB::Error( "Can't open buffer" ); + } + + QTextStream ts( &buffer ); + + QString line; + while ( !( line = ts.readLine() ).isNull() ) { + KBB::Error err = parseLine( line, packages ); + if ( err ) return err; + } + + processResult( packages ); + + return KBB::Error(); +} + +void HtmlParser::init() +{ +} + +void HtmlParser::setPackageListQuery( KURL &url ) +{ + url.setFileName( "query.cgi" ); +} + +KBB::Error HtmlParser::parseLine( const QString &, Bug::List & ) +{ + return KBB::Error(); +} + +KBB::Error HtmlParser::parseLine( const QString &, Package::List & ) +{ + return KBB::Error(); +} + +void HtmlParser::processResult( Package::List & ) +{ +} + +QString HtmlParser::getAttribute( const QString &line, const QString &name ) +{ + int pos1 = line.find( name + "=\"" ); + if ( pos1 < 1 ) return QString::null; + pos1 += name.length() + 2; + int pos2 = line.find( "\"", pos1 ); + if ( pos2 < 1 ) return QString::null; + return line.mid( pos1, pos2 - pos1 ); +} + +bool HtmlParser::getCpts( const QString &line, QString &key, + QStringList &values ) +{ + if ( !line.contains( QRegExp( "\\s*cpts" ) ) ) return false; + +// kdDebug() << "LINE: " << line << endl; + int pos1 = line.find( "[" ); + if ( pos1 < 0 ) return false; + int pos2 = line.find( "]", ++pos1 ); + if ( pos2 < 0 ) return false; + + key = line.mid( pos1, pos2 - pos1 ); + int pos3 = key.find( "'" ); + if ( pos3 >= 0 ) { + int pos4 = key.find( "'", ++pos3 ); + if ( pos4 >= 0 ) key = key.mid( pos3, pos4 - pos3 ); + } +// kdDebug() << " KEY: " << key << endl; + + pos1 = line.find( "'", ++pos2 ); + if ( pos1 >= 0 ) pos2 = line.find( "'", ++pos1 ); + + while ( pos1 >= 0 && pos2 >= 0 ) { + QString value = line.mid( pos1, pos2 - pos1 ); +// kdDebug() << " VALUE: " << value << endl; + + values.append( value ); + + pos1 = line.find( "'", ++pos2 ); + if ( pos1 >= 0 ) pos2 = line.find( "'", ++pos1 ); + } + + return true; +} + +KBB::Error HtmlParser_2_10::parseLine( const QString &line, Bug::List &bugs ) +{ + if ( line.startsWith( "<TR VALIGN" ) ) { +// kdDebug() << "LINE: " << line << endl; + QRegExp re( "show_bug\\.cgi\\?id=(\\d+)" ); + re.search( line ); + QString number = re.cap( 1 ); +// kdDebug() << " NUMBER: " << number << endl; + + QString summary; + int pos = line.findRev( "summary>" ); + if ( pos >= 0 ) summary = line.mid( pos + 8 ); + + Bug bug( new BugImpl( summary, Person(), number, 0xFFFFFFFF, Bug::SeverityUndefined, + Person(), Bug::StatusUndefined, + Bug::BugMergeList() ) ); + + if ( !bug.isNull() ) { + bugs.append( bug ); + } + } + + return KBB::Error(); +} + +KBB::Error HtmlParser_2_10::parseLine( const QString &line, + Package::List &packages ) +{ + QString package; + QStringList components; + + if ( getCpts( line, package, components ) ) { + packages.append( Package( new PackageImpl( package, "", 0, Person(), + components ) ) ); + } + + return KBB::Error(); +} + + +void HtmlParser_2_14_2::init() +{ + mComponentsMap.clear(); + + mState = Idle; +} + +KBB::Error HtmlParser_2_14_2::parseLine( const QString &line, + Package::List & ) +{ + switch ( mState ) { + case Idle: + if ( line.startsWith( "tms[" ) ) mState = Components; + break; + case Components: { + if ( line.startsWith( "function" ) ) mState = Finished; + QString key; + QStringList values; + if ( getCpts( line, key, values ) ) { +// kdDebug() << "KEY: " << key << " VALUES: " << values.join(",") << endl; + if ( values.count() == 2 ) { + mComponentsMap[ values.last() ].append( key ); + } + } + } + default: + break; + } + + return KBB::Error(); +} + +void HtmlParser_2_14_2::processResult( Package::List &packages ) +{ + QMap<QString,QStringList>::ConstIterator it; + for ( it = mComponentsMap.begin(); it != mComponentsMap.end(); ++it ) { + packages.append( Package( new PackageImpl( it.key(), "", 0, Person(), + it.data() ) ) ); + } +} + + +void HtmlParser_2_17_1::init() +{ + mProducts.clear(); + mComponents.clear(); + + mState = Idle; +} + +KBB::Error HtmlParser_2_17_1::parseBugList( const QByteArray &data, Bug::List &bugs ) +{ + return RdfProcessor::parseBugList( data, bugs ); +} + +KBB::Error HtmlParser_2_17_1::parseLine( const QString & /*line*/, Bug::List &/*bugs*/ ) +{ + return KBB::Error( "Not implemented" ); +} + +KBB::Error HtmlParser_2_17_1::parseLine( const QString &line, Package::List & ) +{ + switch ( mState ) { + case Idle: + case SearchComponents: + if ( line.contains( "var cpts" ) ) mState = Components; + break; + case SearchProducts: + if ( line.contains( "onchange=\"selectProduct" ) ) mState = Products; + break; + case Components: { + if ( line.contains( QRegExp( "\\s*function" ) ) ) { + mState = SearchProducts; + } + QString key; + QStringList components; + if ( getCpts( line, key, components ) ) { + mComponents.append( components ); + } + } + case Products: { + if ( line.contains( "</select>" ) ) mState = Finished; + QString product = getAttribute( line, "value" ); + if ( !product.isEmpty() ) { + kdDebug() << "PRODUCT: " << product << endl; + mProducts.append( product ); + } + break; + } + case Finished: + default: + break; + } + + return KBB::Error(); +} + +void HtmlParser_2_17_1::processResult( Package::List &packages ) +{ + QStringList::ConstIterator itProduct = mProducts.begin(); + QValueList<QStringList>::ConstIterator itComponents = mComponents.begin(); + + while( itProduct != mProducts.end() && itComponents != mComponents.end() ) { + packages.append( Package( new PackageImpl( *itProduct, "", 0, Person(), + *itComponents ) ) ); + ++itProduct; + ++itComponents; + } +} diff --git a/kbugbuster/backend/htmlparser.h b/kbugbuster/backend/htmlparser.h new file mode 100644 index 00000000..ffb0a22a --- /dev/null +++ b/kbugbuster/backend/htmlparser.h @@ -0,0 +1,116 @@ +/* + This file is part of KBugBuster. + + Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ +#ifndef HTMLPARSER_H +#define HTMLPARSER_H + +#include "package.h" +#include "bug.h" +#include "error.h" +#include "rdfprocessor.h" + +#include <qstringlist.h> +#include <qvaluelist.h> +#include <qmap.h> + +class HtmlParser : public RdfProcessor +{ + protected: + enum State { Idle, SearchComponents, SearchProducts, Components, Products, + Finished }; + State mState; + + public: + HtmlParser( BugServer *s ) : RdfProcessor( s ), mState( Idle ) {} + virtual ~HtmlParser() {} + + KBB::Error parseBugList( const QByteArray &data, Bug::List &bugs ); + KBB::Error parsePackageList( const QByteArray &data, + Package::List &packages ); + + void setPackageListQuery( KURL & ); + + protected: + virtual void init(); + + virtual KBB::Error parseLine( const QString &line, Bug::List &bugs ); + virtual KBB::Error parseLine( const QString &line, + Package::List &packages ); + + virtual void processResult( Package::List &packages ); + + QString getAttribute( const QString &line, const QString &name ); + bool getCpts( const QString &line, QString &key, QStringList &values ); +}; + + +class HtmlParser_2_10 : public HtmlParser +{ + public: + HtmlParser_2_10( BugServer *s ) : HtmlParser( s ) {} + + protected: + KBB::Error parseLine( const QString &line, Bug::List &bugs ); + KBB::Error parseLine( const QString &line, Package::List &packages ); +}; + + +class HtmlParser_2_14_2 : public HtmlParser_2_10 +{ + public: + HtmlParser_2_14_2( BugServer *s ) : HtmlParser_2_10( s ) {} + + protected: + void init(); + + KBB::Error parseLine( const QString &line, Package::List &packages ); + + void processResult( Package::List &packages ); + + private: + QMap<QString, QStringList> mComponentsMap; +}; + + + +class HtmlParser_2_17_1 : public HtmlParser +{ + public: + HtmlParser_2_17_1( BugServer *s ) : HtmlParser( s ) {} + + KBB::Error parseBugList( const QByteArray &data, Bug::List &bugs ); + + protected: + void init(); + + KBB::Error parseLine( const QString &line, Bug::List &bugs ); + KBB::Error parseLine( const QString &line, Package::List &packages ); + + void processResult( Package::List &packages ); + + private: + QStringList mProducts; + QValueList<QStringList> mComponents; +}; + +#endif diff --git a/kbugbuster/backend/kbbprefs.cpp b/kbugbuster/backend/kbbprefs.cpp new file mode 100644 index 00000000..30f337ab --- /dev/null +++ b/kbugbuster/backend/kbbprefs.cpp @@ -0,0 +1,170 @@ +/* + This file is part of KBugBuster. + + Copyright (c) 2001,2003 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ + +#include <qstring.h> +#include <qstringlist.h> + +#include <klocale.h> +#include <kdebug.h> +#include <kconfig.h> + +#include "bugsystem.h" +#include "bugserver.h" +#include "bugserverconfig.h" + +#include "kbbprefs.h" + +KBBPrefs *KBBPrefs::mInstance = 0; + +KBBPrefs::KBBPrefs() : KConfigSkeleton() +{ + setCurrentGroup("History"); + + addItemInt("RecentPackagesCount",mRecentPackagesCount,7); + addItemIntList("Splitter1",mSplitter1); + addItemIntList("Splitter2",mSplitter2); + + + setCurrentGroup("Personal Settings"); + + addItemInt("MailClient",mMailClient,MailSender::KMail,"Mail Client"); + addItemBool("ShowClosedBugs",mShowClosedBugs,false); + addItemBool("ShowWishes",mShowWishes,true); + addItemBool("ShowVotes", mShowVoted, false); + addItemInt("MinimumVotes", mMinVotes, 0); + addItemBool("SendBCC",mSendBCC,false); + addItemString("OverrideRecipient",mOverrideRecipient,QString::null); + addItemInt("WrapColumn",mWrapColumn,90); + + + setCurrentGroup("MsgInputDlg"); + + addItemInt("MsgDialogWidth",mMsgDlgWidth); + addItemInt("MsgDialogHeight",mMsgDlgHeight); + addItemIntList("MsgDialogSplitter",mMsgDlgSplitter); + + + setCurrentGroup( "Debug" ); + + addItemBool( "DebugMode", mDebugMode, false ); + + + setCurrentGroup( "Servers" ); + + addItemString("CurrentServer",mCurrentServer); +} + + +KBBPrefs::~KBBPrefs() +{ + delete mInstance; + mInstance = 0; +} + + +KBBPrefs *KBBPrefs::instance() +{ + if (!mInstance) { + mInstance = new KBBPrefs(); + mInstance->readConfig(); + } + + return mInstance; +} + +void KBBPrefs::usrSetDefaults() +{ + setMessageButtonsDefault(); +} + +void KBBPrefs::usrReadConfig() +{ + mMessageButtons.clear(); + + config()->setGroup("MessageButtons"); + QStringList buttonList = config()->readListEntry("ButtonList"); + if (buttonList.isEmpty()) { + setMessageButtonsDefault(); + } else { + QStringList::ConstIterator it; + for(it = buttonList.begin(); it != buttonList.end(); ++it) { + QString text = config()->readEntry(*it); + mMessageButtons.insert(*it,text); + } + } + + BugSystem::self()->readConfig( config() ); +} + +void KBBPrefs::usrWriteConfig() +{ + config()->setGroup("MessageButtons"); + QStringList buttonList; + QMap<QString,QString>::ConstIterator it; + for(it = mMessageButtons.begin();it != mMessageButtons.end();++it) { + buttonList.append(it.key()); + config()->writeEntry(it.key(),it.data()); + } + config()->writeEntry("ButtonList",buttonList); + + BugSystem::self()->writeConfig( config() ); +} + +void KBBPrefs::setMessageButtonsDefault() +{ + mMessageButtons.clear(); + mMessageButtons.insert(i18n("Bug Fixed in CVS"),"Thank you for your bug report.\n" + "The bug that you reported has been identified and has been fixed in the\n" + "latest development (CVS) version of KDE. The bug report will be closed.\n"); + mMessageButtons.insert(i18n("Duplicate Report"),"Thank you for your bug report.\n" + "This bug/feature request has already been reported and this report will\n" + "be marked as a duplicate.\n"); + mMessageButtons.insert(i18n("Packaging Bug"),"Thank you for your bug report.\n" + "The bug that you reported appears to be a packaging bug, due to a\n" + "problem in the way in which your distribution/vendor has packaged\n" + "KDE for distribution.\n" + "The bug report will be closed since it is not a KDE problem.\n" + "Please send the bug report to your distribution/vendor instead.\n"); + mMessageButtons.insert(i18n("Feature Implemented in CVS"),"Thank you for your bug report.\n" + "The feature that you requested has been implemented in the latest\n" + "development (CVS) version of KDE. The feature request will be closed.\n"); + mMessageButtons.insert(i18n("More Information Required"),"Thank you for your bug report.\n" + "You have not provided enough information for us to be able to reproduce\n" + "the bug. Please provide a detailed account of the steps required to\n" + "trigger and reproduce the bug. Without this information, we will not be\n" + "able to reproduce, identify and fix the bug.\n"); + mMessageButtons.insert(i18n("No Longer Applicable"),"Thank you for your bug report.\n" + "The bug that your reported no longer applies to the latest development\n" + "(CVS) version of KDE. This is most probably because it has been fixed,\n" + "the application has been substantially modified or the application no\n" + "longer exists. The bug report will be closed.\n"); + mMessageButtons.insert(i18n("Won't Fix Bug"),"Thank you for your bug report/feature request.\n" + "Unfortunately, this bug will never be fixed or the feature never\n" + "implemented. The bug report/feature request will be closed.\n"); + mMessageButtons.insert(i18n("Cannot Reproduce Bug"),"Thank you for your bug report.\n" + "This bug can not be reproduced using the current development (CVS)\n" + "version of KDE. This suggests that the bug has already been fixed.\n" + "The bug report will be closed.\n"); +} + diff --git a/kbugbuster/backend/kbbprefs.h b/kbugbuster/backend/kbbprefs.h new file mode 100644 index 00000000..64d7f20d --- /dev/null +++ b/kbugbuster/backend/kbbprefs.h @@ -0,0 +1,82 @@ +/* + This file is part of KBugBuster. + + Copyright (c) 2001,2003 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ +#ifndef KBBPREFS_H +#define KBBPREFS_H + +#include <qmap.h> + +#include <kconfigskeleton.h> + +#include "mailsender.h" + +class QStringList; + +class KBBPrefs : public KConfigSkeleton +{ + public: + virtual ~KBBPrefs(); + + static KBBPrefs *instance(); + + protected: + void usrSetDefaults(); + void usrReadConfig(); + void usrWriteConfig(); + + void setMessageButtonsDefault(); + + private: + KBBPrefs(); + + static KBBPrefs *mInstance; + + public: + int mRecentPackagesCount; + + QValueList<int> mSplitter1; + QValueList<int> mSplitter2; + + int mMailClient; + bool mShowClosedBugs; + bool mShowWishes; + bool mSendBCC; + QString mOverrideRecipient; + + bool mShowVoted; + int mMinVotes; + + int mWrapColumn; + + QMap<QString,QString> mMessageButtons; + + int mMsgDlgWidth; + int mMsgDlgHeight; + QValueList<int> mMsgDlgSplitter; + + bool mDebugMode; + + QString mCurrentServer; +}; + +#endif diff --git a/kbugbuster/backend/mailsender.cpp b/kbugbuster/backend/mailsender.cpp new file mode 100644 index 00000000..ec32405d --- /dev/null +++ b/kbugbuster/backend/mailsender.cpp @@ -0,0 +1,212 @@ +#ifndef QT_NO_ASCII_CAST +#define QT_NO_ASCII_CAST +#endif + +#include <unistd.h> +#include <stdio.h> + +#include <qtimer.h> + +#include <klocale.h> +#include <kstandarddirs.h> +#include <kdebug.h> +#include <kmessagebox.h> +#include <kurl.h> +#include <kapplication.h> +#include <dcopclient.h> +#include <kprocess.h> + +#include "mailsender.h" +#include "smtp.h" + +MailSender::MailSender(MailClient client,const QString &smtpServer) : + m_client( client ), m_smtpServer( smtpServer ) +{ +} + +MailSender::~MailSender() +{ +} + +MailSender *MailSender::clone() const +{ + return new MailSender(m_client,m_smtpServer); +} + +bool MailSender::send(const QString &fromName,const QString &fromEmail,const QString &to, + const QString &subject,const QString &body,bool bcc, + const QString &recipient) +{ + QString from( fromName ); + if ( !fromEmail.isEmpty() ) + from += QString::fromLatin1( " <%2>" ).arg( fromEmail ); + kdDebug() << "MailSender::sendMail():\nFrom: " << from << "\nTo: " << to + << "\nbccflag:" << bcc + << "\nRecipient:" << recipient + << "\nSubject: " << subject << "\nBody: \n" << body << endl; + + // ### FIXME: bcc is not supported in direct mode and recipient is not + // supported in sendmail and kmail mode + + if (m_client == Sendmail) { + kdDebug() << "Sending per Sendmail" << endl; + + bool needHeaders = true; + + QString command = KStandardDirs::findExe(QString::fromLatin1("sendmail"), + QString::fromLatin1("/sbin:/usr/sbin:/usr/lib")); + if (!command.isNull()) command += QString::fromLatin1(" -oi -t"); + else { + command = KStandardDirs::findExe(QString::fromLatin1("mail")); + if (command.isNull()) return false; // give up + + command.append(QString::fromLatin1(" -s ")); + command.append(KProcess::quote(subject)); + + if (bcc) { + command.append(QString::fromLatin1(" -b ")); + command.append(KProcess::quote(from)); + } + + command.append(" "); + command.append(KProcess::quote(to)); + + needHeaders = false; + } + + FILE * fd = popen(command.local8Bit(),"w"); + if (!fd) + { + kdError() << "Unable to open a pipe to " << command << endl; + QTimer::singleShot( 0, this, SLOT( deleteLater() ) ); + return false; + } + + QString textComplete; + if (needHeaders) + { + textComplete += QString::fromLatin1("From: ") + from + '\n'; + textComplete += QString::fromLatin1("To: ") + to + '\n'; + if (bcc) textComplete += QString::fromLatin1("Bcc: ") + from + '\n'; + textComplete += QString::fromLatin1("Subject: ") + subject + '\n'; + textComplete += QString::fromLatin1("X-Mailer: KBugBuster") + '\n'; + } + textComplete += '\n'; // end of headers + textComplete += body; + + emit status( i18n( "Sending through sendmail..." ) ); + fwrite(textComplete.local8Bit(),textComplete.length(),1,fd); + + pclose(fd); + } else if ( m_client == KMail ) { + kdDebug() << "Sending per KMail" << endl; + + if (!kapp->dcopClient()->isApplicationRegistered("kmail")) { + KMessageBox::error(0,i18n("No running instance of KMail found.")); + QTimer::singleShot( 0, this, SLOT( deleteLater() ) ); + return false; + } + + emit status( i18n( "Passing mail to KDE email program..." ) ); + if (!kMailOpenComposer(to,"", (bcc ? from : ""), subject,body,0,KURL())) { + QTimer::singleShot( 0, this, SLOT( deleteLater() ) ); + return false; + } + } else if ( m_client == Direct ) { + kdDebug() << "Sending Direct" << endl; + + QStringList recipients; + if ( !recipient.isEmpty() ) + recipients << recipient; + else + recipients << to; + + QString message = QString::fromLatin1( "From: " ) + from + + QString::fromLatin1( "\nTo: " ) + to + + QString::fromLatin1( "\nSubject: " ) + subject + + QString::fromLatin1( "\nX-Mailer: KBugBuster" ) + + QString::fromLatin1( "\n\n" ) + body; + + Smtp *smtp = new Smtp( fromEmail, recipients, message, m_smtpServer ); + connect( smtp, SIGNAL( status( const QString & ) ), + this, SIGNAL( status( const QString & ) ) ); + connect( smtp, SIGNAL( success() ), + this, SLOT( smtpSuccess() ) ); + connect( smtp, SIGNAL( error( const QString &, const QString & ) ), + this, SLOT( smtpError( const QString &, const QString & ) ) ); + + smtp->insertChild( this ); // die when smtp dies + } else { + kdDebug() << "Invalid mail client setting." << endl; + } + + if (m_client != Direct) + { + emit finished(); + QTimer::singleShot( 0, this, SLOT( deleteLater() ) ); + } + + return true; +} + +void MailSender::smtpSuccess() +{ + if ( parent() != sender() || !parent()->inherits( "Smtp" ) ) + return; + + static_cast<Smtp *>( parent() )->quit(); + emit finished(); +} + +void MailSender::smtpError(const QString &_command, const QString &_response) +{ + if ( parent() != sender() || !parent()->inherits( "Smtp" ) ) + return; + + QString command = _command; + QString response = _response; + + Smtp *smtp = static_cast<Smtp *>( parent() ); + smtp->removeChild( this ); + delete smtp; + + KMessageBox::error( qApp->activeWindow(), + i18n( "Error during SMTP transfer.\n" + "command: %1\n" + "response: %2" ).arg( command ).arg( response ) ); + + emit finished(); + QTimer::singleShot( 0, this, SLOT( deleteLater() ) ); +} + +int MailSender::kMailOpenComposer(const QString& arg0,const QString& arg1, + const QString& arg2,const QString& arg3,const QString& arg4,int arg5, + const KURL& arg6) +{ + int result = 0; + + QByteArray data, replyData; + QCString replyType; + QDataStream arg( data, IO_WriteOnly ); + arg << arg0; + arg << arg1; + arg << arg2; + arg << arg3; + arg << arg4; + arg << arg5; + arg << arg6; + if (kapp->dcopClient()->call("kmail","KMailIface","openComposer(QString,QString,QString,QString,QString,int,KURL)", data, replyType, replyData ) ) { + if ( replyType == "int" ) { + QDataStream _reply_stream( replyData, IO_ReadOnly ); + _reply_stream >> result; + } else { + kdDebug() << "kMailOpenComposer() call failed." << endl; + } + } else { + kdDebug() << "kMailOpenComposer() call failed." << endl; + } + return result; +} + +#include "mailsender.moc" + diff --git a/kbugbuster/backend/mailsender.h b/kbugbuster/backend/mailsender.h new file mode 100644 index 00000000..06517f9c --- /dev/null +++ b/kbugbuster/backend/mailsender.h @@ -0,0 +1,50 @@ +#ifndef MAILSENDER_H +#define MAILSENDER_H + +#include <qstring.h> +#include <qobject.h> + +class KURL; +class Smtp; + +class MailSender : public QObject +{ + Q_OBJECT + public: + enum MailClient { Sendmail = 0, KMail = 1, Direct = 2 }; + + MailSender(MailClient,const QString &smtpServer=QString::null); + virtual ~MailSender(); + + MailSender *clone() const; + + /** + Send mail with specified from, to and subject field and body as text. If + bcc is set, send a blind carbon copy to the sender from. + If recipient is specified the mail is sent to the specified address + instead of 'to' . (this currently only works in for direct mail + sending through SMTP. + */ + bool send(const QString &fromName, const QString &fromEmail, + const QString &to,const QString &subject, + const QString &body,bool bcc=false, + const QString &recipient = QString::null); + + signals: + void status( const QString &message ); + void finished(); + + private slots: + void smtpSuccess(); + void smtpError(const QString &command, const QString &response); + + private: + int kMailOpenComposer(const QString& arg0,const QString& arg1, + const QString& arg2,const QString& arg3, + const QString& arg4,int arg5,const KURL& arg6); + + MailClient m_client; + QString m_smtpServer; +}; + +#endif diff --git a/kbugbuster/backend/package.cpp b/kbugbuster/backend/package.cpp new file mode 100644 index 00000000..ae009397 --- /dev/null +++ b/kbugbuster/backend/package.cpp @@ -0,0 +1,82 @@ + +#include "package.h" + +#include "packageimpl.h" + +Package::Package() +{ +} + +Package::Package( PackageImpl *impl ) + : m_impl( impl ) +{ +} + +Package::Package( const Package &other ) +{ + (*this) = other; +} + +Package &Package::operator=( const Package &rhs ) +{ + m_impl = rhs.m_impl; + return *this; +} + +Package::~Package() +{ +} + +QString Package::name() const +{ + if ( !m_impl ) + return QString::null; + + return m_impl->name; +} + +QString Package::description() const +{ + if ( !m_impl ) + return QString::null; + + return m_impl->description; +} + +uint Package::numberOfBugs() const +{ + if ( !m_impl ) + return 0; + + return m_impl->numberOfBugs; +} + +Person Package::maintainer() const +{ + if ( !m_impl ) + return Person(); + + return m_impl->maintainer; +} + +const QStringList Package::components() const +{ + if ( !m_impl ) + return QStringList(); + + return m_impl->components; +} + +bool Package::operator==( const Package &rhs ) +{ + return m_impl == rhs.m_impl; +} + +bool Package::operator<( const Package &rhs ) const +{ + return m_impl < rhs.m_impl; +} + +/** + * vim:ts=4:sw=4:et + */ diff --git a/kbugbuster/backend/package.h b/kbugbuster/backend/package.h new file mode 100644 index 00000000..7b5c69a1 --- /dev/null +++ b/kbugbuster/backend/package.h @@ -0,0 +1,43 @@ +#ifndef __package_h__ +#define __package_h__ + +#include "person.h" + +#include <qvaluelist.h> + +#include <ksharedptr.h> + +class PackageImpl; + +class Package +{ +public: + typedef QValueList<Package> List; + + Package(); + Package( PackageImpl *impl ); + Package( const Package &other ); + Package &operator=( const Package &rhs ); + ~Package(); + + QString name() const; + QString description() const; + uint numberOfBugs() const; + Person maintainer() const; + const QStringList components() const; + + bool isNull() const { return m_impl == 0; } + + PackageImpl *impl() const { return m_impl; } + + bool operator==( const Package &rhs ); + bool operator<( const Package &rhs ) const; + +private: + KSharedPtr<PackageImpl> m_impl; +}; + +#endif + +/* vim: set sw=4 ts=4 et softtabstop=4: */ + diff --git a/kbugbuster/backend/packageimpl.h b/kbugbuster/backend/packageimpl.h new file mode 100644 index 00000000..c60a1079 --- /dev/null +++ b/kbugbuster/backend/packageimpl.h @@ -0,0 +1,31 @@ +#ifndef __packageimpl_h__ +#define __packageimpl_h__ + +#include "person.h" + +#include <qstringlist.h> +#include <kurl.h> +#include <ksharedptr.h> + +struct PackageImpl : public KShared +{ +public: + PackageImpl( const QString &_name, const QString &_description, + uint _numberOfBugs, const Person &_maintainer, + const QStringList &_components ) + : name( _name ), description( _description ),numberOfBugs( _numberOfBugs ), + maintainer( _maintainer ), components(_components) + {} + + QString name; + QString description; + uint numberOfBugs; + Person maintainer; + QStringList components; +}; + +#endif + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/packagelistjob.cpp b/kbugbuster/backend/packagelistjob.cpp new file mode 100644 index 00000000..6b05badf --- /dev/null +++ b/kbugbuster/backend/packagelistjob.cpp @@ -0,0 +1,68 @@ +/* + This file is part of KBugBuster. + + Copyright (c) 2002,2003 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ + +#include "packagelistjob.h" +#include "package.h" +#include "packageimpl.h" +#include "bugserver.h" +#include "domprocessor.h" +#include "htmlparser.h" + +#include <kdebug.h> +#include <assert.h> + +#include <qdom.h> +#include <qbuffer.h> + +PackageListJob::PackageListJob( BugServer *server ) + : BugJob( server ) +{ +} + +PackageListJob::~PackageListJob() +{ +} + +void PackageListJob::start() +{ + BugJob::start( server()->packageListUrl() ); +} + +void PackageListJob::process( const QByteArray &data ) +{ + Package::List packages; + KBB::Error err = server()->processor()->parsePackageList( data, packages ); + if ( err ) { + emit error( err.message() ); + } else { + emit packageListAvailable( packages ); + } +} + + +#include "packagelistjob.moc" + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/packagelistjob.h b/kbugbuster/backend/packagelistjob.h new file mode 100644 index 00000000..0e1bca99 --- /dev/null +++ b/kbugbuster/backend/packagelistjob.h @@ -0,0 +1,55 @@ +/* + This file is part of KBugBuster. + + Copyright (c) 2002,2003 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ + +#ifndef __packagelistjob_h__ +#define __packagelistjob_h__ + +#include "bugjob.h" + +#include "package.h" + +#include <qdom.h> + +class PackageListJob : public BugJob +{ + Q_OBJECT + public: + PackageListJob( BugServer * ); + virtual ~PackageListJob(); + + void start(); + + protected: + void process( const QByteArray &data ); + + signals: + void packageListAvailable( const Package::List &pkgs ); +}; + + +#endif + +/* + * vim:ts=4:sw=4:et + */ diff --git a/kbugbuster/backend/person.cpp b/kbugbuster/backend/person.cpp new file mode 100644 index 00000000..a9f63be0 --- /dev/null +++ b/kbugbuster/backend/person.cpp @@ -0,0 +1,74 @@ +#include <kdebug.h> + +#include "person.h" + +Person::Person( const QString &fullName ) +{ + int emailPos = fullName.find( '<' ); + if ( emailPos < 0 ) { + email = fullName; + } else { + email = fullName.mid( emailPos + 1, fullName.length() - 1 ); + name = fullName.left( emailPos - 1 ); + } +} + +QString Person::fullName(bool html) const +{ + if( name.isEmpty() ) + { + if( email.isEmpty() ) + return i18n( "Unknown" ); + else + return email; + } + else + { + if( email.isEmpty() ) + return name; + else + if ( html ) { + return name + " <" + email + ">"; + } else { + return name + " <" + email + ">"; + } + } +} + +Person Person::parseFromString( const QString &_str ) +{ + Person res; + + QString str = _str; + + int ltPos = str.find( '<' ); + if ( ltPos != -1 ) + { + int gtPos = str.find( '>', ltPos ); + if ( gtPos != -1 ) + { + res.name = str.left( ltPos - 1 ); + str = str.mid( ltPos + 1, gtPos - ( ltPos + 1 ) ); + } + } + + int atPos = str.find( '@' ); + int spacedAtPos = str.find( QString::fromLatin1( " at " ) ); + if ( atPos == -1 && spacedAtPos != -1 ) + str.replace( spacedAtPos, 4, QString::fromLatin1( "@" ) ); + + int spacePos = str.find( ' ' ); + while ( spacePos != -1 ) + { + str[ spacePos ] = '.'; + spacePos = str.find( ' ', spacePos ); + } + + res.email = str; + + return res; +} + +/** + * vim:et:ts=4:sw=4 + */ diff --git a/kbugbuster/backend/person.h b/kbugbuster/backend/person.h new file mode 100644 index 00000000..f29074f9 --- /dev/null +++ b/kbugbuster/backend/person.h @@ -0,0 +1,26 @@ +#ifndef __person_h__ +#define __person_h__ + +#include <qstring.h> +#include <klocale.h> + +struct Person +{ + Person() {} + Person( const QString &fullName ); + Person( const QString &_name, const QString &_email ) + : name( _name ), email( _email ) {} + + QString name; + QString email; + + QString fullName( bool html = false ) const; + + static Person parseFromString( const QString &str ); +}; + +#endif + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/processor.cpp b/kbugbuster/backend/processor.cpp new file mode 100644 index 00000000..332b4418 --- /dev/null +++ b/kbugbuster/backend/processor.cpp @@ -0,0 +1,78 @@ +/* + This file is part of KBugBuster. + + Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ + +#include <qstylesheet.h> + +#include <kdebug.h> +#include <kmdcodec.h> + +#include "processor.h" + +#include "bugserver.h" +#include "packageimpl.h" +#include "bugimpl.h" +#include "bugdetailsimpl.h" +#include "kbbprefs.h" + +Processor::Processor( BugServer *server ) + : mServer( server ) +{ +} + +Processor::~Processor() +{ +} + +void Processor::setPackageListQuery( KURL &url ) +{ + url.setFileName( "xml.cgi" ); + url.setQuery( "?data=versiontable" ); +} + +void Processor::setBugListQuery( KURL &url, const Package &product, const QString &component ) +{ + if ( mServer->serverConfig().bugzillaVersion() == "Bugworld" ) { + url.setFileName( "bugworld.cgi" ); + } else { + url.setFileName( "xmlquery.cgi" ); + } + + QString user = mServer->serverConfig().user(); + + if ( component.isEmpty() ) + url.setQuery( "?user=" + user + "&product=" + product.name() ); + else + url.setQuery( "?user=" + user + "&product=" + product.name() + "&component=" + component ); +} + +void Processor::setBugDetailsQuery( KURL &url, const Bug &bug ) +{ + url.setFileName( "xml.cgi" ); + url.setQuery( "?id=" + bug.number() ); +} + + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/processor.h b/kbugbuster/backend/processor.h new file mode 100644 index 00000000..cadaa407 --- /dev/null +++ b/kbugbuster/backend/processor.h @@ -0,0 +1,63 @@ +/* + This file is part of KBugBuster. + + Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ +#ifndef KBB_PROCESSOR_H +#define KBB_PROCESSOR_H + +#include "bug.h" +#include "bugdetails.h" +#include "package.h" +#include "error.h" + +#include <kurl.h> + +class BugServer; + +class Processor +{ + public: + Processor( BugServer * ); + virtual ~Processor(); + + BugServer *server() const { return mServer; } + + virtual KBB::Error parseBugList( const QByteArray &data, + Bug::List &bugs ) = 0; + virtual KBB::Error parsePackageList( const QByteArray &data, + Package::List &packages ) = 0; + virtual KBB::Error parseBugDetails( const QByteArray &, BugDetails & ) = 0; + + virtual void setPackageListQuery( KURL & ) = 0; + virtual void setBugListQuery( KURL &, const Package &, + const QString &component ) = 0; + virtual void setBugDetailsQuery( KURL &, const Bug & ) = 0; + + private: + BugServer *mServer; +}; + +#endif + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/rdfprocessor.cpp b/kbugbuster/backend/rdfprocessor.cpp new file mode 100644 index 00000000..89bb3402 --- /dev/null +++ b/kbugbuster/backend/rdfprocessor.cpp @@ -0,0 +1,107 @@ +/* + This file is part of KBugBuster. + Copyright (c) 2002 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ + +#include "rdfprocessor.h" + +#include "bugserver.h" +#include "packageimpl.h" +#include "bugimpl.h" +#include "bugdetailsimpl.h" +#include "kbbprefs.h" + +#include "kdebug.h" + +RdfProcessor::RdfProcessor( BugServer *server ) + : DomProcessor( server ) +{ +} + +RdfProcessor::~RdfProcessor() +{ +} + +KBB::Error RdfProcessor::parseDomBugList( const QDomElement &element, + Bug::List &bugs ) +{ + if ( element.tagName() != "RDF" ) { + kdDebug() << "RdfProcessor::parseBugList(): no RDF element." << endl; + return KBB::Error( "No RDF element found" ); + } + + QDomNodeList bugNodes = element.elementsByTagName( "bz:bug" ); + + for( uint i = 0; i < bugNodes.count(); ++i ) { + QString title; + Person submitter; + QString bugNr; + Bug::Status status = Bug::StatusUndefined; + Bug::Severity severity = Bug::SeverityUndefined; + Person developerTodo; + Bug::BugMergeList mergedList; + + QDomNode hit = bugNodes.item( i ); + + QDomNode n; + for( n = hit.firstChild(); !n.isNull(); n = n.nextSibling() ) { + QDomElement e = n.toElement(); + + if ( e.tagName() == "bz:id" ) { + bugNr = e.text(); + } else if ( e.tagName() == "bz:status" ) { + status = server()->bugStatus( e.text() ); + } else if ( e.tagName() == "bz:severity" ) { + severity = server()->bugSeverity( e.text() ); + } else if ( e.tagName() == "bz:summary" ) { + title = e.text(); + } + } + + Bug bug( new BugImpl( title, submitter, bugNr, 0xFFFFFFFF, severity, + developerTodo, status, mergedList ) ); + + if ( !bug.isNull() ) { + bugs.append( bug ); + } + } + + return KBB::Error(); +} + +void RdfProcessor::setBugListQuery( KURL &url, const Package &product, const QString &component ) +{ + url.setFileName( "buglist.cgi" ); + if ( component.isEmpty() ) + url.setQuery( "?format=rdf&product=" + product.name() ); + else + url.setQuery( "?format=rdf&product=" + product.name() + "&component=" + component ); + if ( KBBPrefs::instance()->mShowVoted ) { + url.addQueryItem( "field0-0-0", "votes" ); + url.addQueryItem( "type0-0-0", "greaterthan" ); + QString num = QString::number( KBBPrefs::instance()->mMinVotes );; + url.addQueryItem( "value0-0-0", num ); + } +} + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/rdfprocessor.h b/kbugbuster/backend/rdfprocessor.h new file mode 100644 index 00000000..0e0bd780 --- /dev/null +++ b/kbugbuster/backend/rdfprocessor.h @@ -0,0 +1,43 @@ +/* + This file is part of KBugBuster. + Copyright (c) 2002 Cornelius Schumacher <schumacher@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ +#ifndef RDFPROCESSOR_H +#define RDFPROCESSOR_H + +#include "domprocessor.h" + +class RdfProcessor : public DomProcessor +{ + public: + RdfProcessor( BugServer * ); + virtual ~RdfProcessor(); + + KBB::Error parseDomBugList( const QDomElement &, Bug::List & ); + + void setBugListQuery( KURL &, const Package &, const QString &component ); +}; + +#endif + +/* + * vim:sw=4:ts=4:et + */ diff --git a/kbugbuster/backend/smtp.cpp b/kbugbuster/backend/smtp.cpp new file mode 100644 index 00000000..d54cafab --- /dev/null +++ b/kbugbuster/backend/smtp.cpp @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** This file is a modified version of part of an example program for Qt. +** This file may be used, distributed and modified without limitation. +** +** Don Sanders <sanders@kde.org> +** +*****************************************************************************/ + +#include "smtp.h" + +#include <qtextstream.h> +#include <qsocket.h> +#include <qtimer.h> +#include <kapplication.h> +#include <kmessagebox.h> +#include <klocale.h> + +Smtp::Smtp( const QString &from, const QStringList &to, + const QString &aMessage, + const QString &server, + unsigned short int port ) +{ + skipReadResponse = false; + mSocket = new QSocket( this ); + connect ( mSocket, SIGNAL( readyRead() ), + this, SLOT( readyRead() ) ); + connect ( mSocket, SIGNAL( connected() ), + this, SLOT( connected() ) ); + connect ( mSocket, SIGNAL( error(int) ), + this, SLOT( socketError(int) ) ); + + message = aMessage; + + this->from = from; + rcpt = to; + state = smtpInit; + command = ""; + + emit status( i18n( "Connecting to %1" ).arg( server ) ); + + mSocket->connectToHost( server, port ); + t = new QTextStream( mSocket ); + t->setEncoding(QTextStream::Latin1); +} + + +Smtp::~Smtp() +{ + if (t) + delete t; + if (mSocket) + delete mSocket; +} + + +void Smtp::send( const QString &from, const QStringList &to, + const QString &aMessage ) +{ + skipReadResponse = true; + message = aMessage; + this->from = from; + rcpt = to; + + state = smtpMail; + command = ""; + readyRead(); +} + + +void Smtp::quit() +{ + skipReadResponse = true; + state = smtpQuit; + command = ""; + readyRead(); +} + + +void Smtp::connected() +{ + emit status( i18n( "Connected to %1" ).arg( mSocket->peerName() ) ); +} + +void Smtp::socketError(int errorCode) +{ + command = "CONNECT"; + switch ( errorCode ) { + case QSocket::ErrConnectionRefused: + responseLine = i18n( "Connection refused." ); + break; + case QSocket::ErrHostNotFound: + responseLine = i18n( "Host Not Found." ); + break; + case QSocket::ErrSocketRead: + responseLine = i18n( "Error reading socket." ); + break; + default: + responseLine = i18n( "Internal error, unrecognized error." ); + } + QTimer::singleShot( 0, this, SLOT(emitError()) ); +} + +void Smtp::emitError() { + error( command, responseLine ); +} + +void Smtp::readyRead() +{ + if (!skipReadResponse) { + // SMTP is line-oriented + if ( !mSocket->canReadLine() ) + return; + + do { + responseLine = mSocket->readLine(); + response += responseLine; + } while( mSocket->canReadLine() && responseLine[3] != ' ' ); + } + skipReadResponse = false; + + if ( state == smtpInit && responseLine[0] == '2' ) { + // banner was okay, let's go on + command = "HELO there"; + *t << "HELO there\r\n"; + state = smtpMail; + } else if ( state == smtpMail && responseLine[0] == '2' ) { + // HELO response was okay (well, it has to be) + command = "MAIL"; + *t << "MAIL FROM: <" << from << ">\r\n"; + state = smtpRcpt; + } else if ( state == smtpRcpt && responseLine[0] == '2' && (rcpt.begin() != rcpt.end())) { + command = "RCPT"; + *t << "RCPT TO: <" << *(rcpt.begin()) << ">\r\n"; + rcpt.remove( rcpt.begin() ); + if (rcpt.begin() == rcpt.end()) + state = smtpData; + } else if ( state == smtpData && responseLine[0] == '2' ) { + command = "DATA"; + *t << "DATA\r\n"; + state = smtpBody; + } else if ( state == smtpBody && responseLine[0] == '3' ) { + command = "DATA"; + QString seperator = ""; + if (message[message.length() - 1] != '\n') + seperator = "\r\n"; + *t << message << seperator << ".\r\n"; + state = smtpSuccess; + } else if ( state == smtpSuccess && responseLine[0] == '2' ) { + QTimer::singleShot( 0, this, SIGNAL(success()) ); + } else if ( state == smtpQuit && responseLine[0] == '2' ) { + command = "QUIT"; + *t << "QUIT\r\n"; + // here, we just close. + state = smtpClose; + emit status( i18n( "Message sent" ) ); + } else if ( state == smtpClose ) { + // we ignore it + } else { // error occurred + QTimer::singleShot( 0, this, SLOT(emitError()) ); + state = smtpClose; + } + + response = ""; + + if ( state == smtpClose ) { + delete t; + t = 0; + delete mSocket; + mSocket = 0; + QTimer::singleShot( 0, this, SLOT(deleteMe()) ); + } +} + + +void Smtp::deleteMe() +{ + delete this; +} + +#include "smtp.moc" diff --git a/kbugbuster/backend/smtp.h b/kbugbuster/backend/smtp.h new file mode 100644 index 00000000..d800cb77 --- /dev/null +++ b/kbugbuster/backend/smtp.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** This file is a modified version of part of an example program for Qt. +** This file may be used, distributed and modified without limitation. +** +** Don Sanders <sanders@kde.org> +** +*****************************************************************************/ + +#ifndef SMTP_H +#define SMTP_H + +#include <qobject.h> +#include <qstring.h> +#include <qstringlist.h> + +class QSocket; +class QTextStream; + +class Smtp : public QObject +{ + Q_OBJECT + +public: + Smtp( const QString &from, const QStringList &to, const QString &message, + const QString &server, unsigned short int port = 25 ); + ~Smtp(); + void send( const QString &, const QStringList &, const QString & ); + void quit(); + + +signals: + void success(); + void status( const QString & ); + void error( const QString &command, const QString &response ); + +private slots: + void readyRead(); + void connected(); + void deleteMe(); + void socketError(int err); + void emitError(); + +private: + enum State { + smtpInit, + smtpMail, + smtpRcpt, + smtpData, + smtpBody, + smtpSuccess, + smtpQuit, + smtpClose + }; + + QString message; + QString from; + QStringList rcpt; + QSocket *mSocket; + QTextStream * t; + int state; + QString response, responseLine; + bool skipReadResponse; + QString command; +}; + +#endif |