diff --git a/incidenceeditor-ng/incidenceattendee.cpp b/incidenceeditor-ng/incidenceattendee.cpp index 1c859bee17..d950603a1d 100644 --- a/incidenceeditor-ng/incidenceattendee.cpp +++ b/incidenceeditor-ng/incidenceattendee.cpp @@ -1,1060 +1,1065 @@ /* Copyright (C) 2010 Casey Link Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company Based on old attendeeeditor.cpp: Copyright (c) 2000,2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "incidenceattendee.h" #include "attendeeeditor.h" #include "conflictresolver.h" #include "editorconfig.h" #include "incidencedatetime.h" #include "schedulingdialog.h" #include "attendeecomboboxdelegate.h" #include "freebusymodel/freebusyitemmodel.h" #ifdef KDEPIM_MOBILE_UI #include "ui_dialogmoremobile.h" #else #include "ui_dialogdesktop.h" #endif #include #include #include #include #include #include #include #include using namespace IncidenceEditorNG; #ifdef KDEPIM_MOBILE_UI IncidenceAttendee::IncidenceAttendee( QWidget *parent, IncidenceDateTime *dateTime, Ui::EventOrTodoMore *ui ) #else IncidenceAttendee::IncidenceAttendee( QWidget *parent, IncidenceDateTime *dateTime, Ui::EventOrTodoDesktop *ui ) #endif : mUi( ui ), mParentWidget( parent ), mStateDelegate(new AttendeeComboBoxDelegate(this)), mRoleDelegate(new AttendeeComboBoxDelegate(this)), mResponseDelegate(new AttendeeComboBoxDelegate(this)), mConflictResolver( 0 ), mDateTime( dateTime ) { KCalCore::Attendee::List attendees; KCalCore::Attendee::Ptr attendee(new KCalCore::Attendee("", "")); attendees.append(attendee); mDataModel = new AttendeeTableModel(attendees, this); mDataModel->setKeepEmpty(true); mDataModel->setRemoveEmptyLines(true); #ifdef KDEPIM_MOBILE_UI mRoleDelegate->addItem(DesktopIcon("meeting-participant", 48), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::ReqParticipant)); mRoleDelegate->addItem(DesktopIcon("meeting-participant-optional", 48), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::OptParticipant)); mRoleDelegate->addItem(DesktopIcon("meeting-observer", 48), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::NonParticipant)); mRoleDelegate->addItem(DesktopIcon("meeting-chair", 48), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::Chair)); mResponseDelegate->addItem( DesktopIcon( "meeting-participant-request-response", 48 ), i18nc( "@item:inlistbox", "Request Response" ) ); mResponseDelegate->addItem( DesktopIcon( "meeting-participant-no-response", 48 ), i18nc( "@item:inlistbox", "Request No Response" ) ); #else mRoleDelegate->addItem(SmallIcon("meeting-participant"), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::ReqParticipant)); mRoleDelegate->addItem(SmallIcon("meeting-participant-optional"), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::OptParticipant)); mRoleDelegate->addItem(SmallIcon("meeting-observer"), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::NonParticipant)); mRoleDelegate->addItem(SmallIcon("meeting-chair"), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::Chair)); mResponseDelegate->addItem( SmallIcon( "meeting-participant-request-response" ), i18nc( "@item:inlistbox", "Request Response" ) ); mResponseDelegate->addItem( SmallIcon( "meeting-participant-no-response" ), i18nc( "@item:inlistbox", "Request No Response" ) ); #endif mStateDelegate->setWhatsThis( i18nc( "@info:whatsthis", "Edits the current attendance status of the attendee." ) ); mRoleDelegate->setWhatsThis( i18nc( "@info:whatsthis", "Edits the role of the attendee." ) ); mResponseDelegate->setToolTip( i18nc( "@info:tooltip", "Request a response from the attendee" ) ); mResponseDelegate->setWhatsThis( i18nc( "@info:whatsthis", "Edits whether to send an email to the " "attendee to request a response concerning " "attendance." ) ); setObjectName( "IncidenceAttendee" ); AttendeeFilterProxyModel *filterProxyModel = new AttendeeFilterProxyModel(this); filterProxyModel->setDynamicSortFilter(true); filterProxyModel->setSourceModel(mDataModel); #ifdef KDEPIM_MOBILE_UI #else connect(mUi->mGroupSubstitution, SIGNAL(clicked(bool)), SLOT(slotGroupSubstitutionPressed())); mUi->mAttendeeTable->setModel(filterProxyModel); mAttendeeDelegate = new AttendeeLineEditDelegate(this); mAttendeeDelegate->setCompletionMode( KGlobalSettings::self()->completionMode() ); mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::Role, roleDelegate()); mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::FullName, attendeeDelegate()); mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::Status, stateDelegate()); mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::Response, responseDelegate()); #endif mUi->mOrganizerStack->setCurrentIndex( 0 ); fillOrganizerCombo(); mUi->mSolveButton->setEnabled( false ); mUi->mOrganizerLabel->setVisible( false ); mConflictResolver = new ConflictResolver( parent, parent ); - mConflictResolver->setEarliestDate( mDateTime->startDate() ); - mConflictResolver->setEarliestTime( mDateTime->startTime() ); - mConflictResolver->setLatestDate( mDateTime->endDate() ); - mConflictResolver->setLatestTime( mDateTime->endTime() ); + mConflictResolver->setEarliestDateTime( mDateTime->currentStartDateTime() ); + mConflictResolver->setLatestDateTime( mDateTime->currentEndDateTime() ); connect( mUi->mSelectButton, SIGNAL(clicked(bool)), this, SLOT(slotSelectAddresses()) ); connect( mUi->mSolveButton, SIGNAL(clicked(bool)), this, SLOT(slotSolveConflictPressed()) ); /* Added as part of kolab/issue2297, which is currently under review connect( mUi->mOrganizerCombo, SIGNAL(activated(QString)), this, SLOT(slotOrganizerChanged(QString)) ); */ connect( mUi->mOrganizerCombo, SIGNAL(currentIndexChanged(int)), SLOT(checkDirtyStatus()) ); connect( mDateTime, SIGNAL(startDateChanged(QDate)), this, SLOT(slotEventDurationChanged()) ); connect( mDateTime, SIGNAL(endDateChanged(QDate)), this, SLOT(slotEventDurationChanged()) ); connect( mDateTime, SIGNAL(startTimeChanged(QTime)), this, SLOT(slotEventDurationChanged()) ); connect( mDateTime, SIGNAL(endTimeChanged(QTime)), this, SLOT(slotEventDurationChanged()) ); connect( mConflictResolver, SIGNAL(conflictsDetected(int)), this, SLOT(slotUpdateConflictLabel(int)) ); connect( mConflictResolver->model(), SIGNAL(rowsInserted(const QModelIndex&, int, int)), SLOT(slotFreeBusyAdded(const QModelIndex&, int, int)) ); connect(mConflictResolver->model(), SIGNAL(layoutChanged()), SLOT(updateFBStatus())); connect(mConflictResolver->model(), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), SLOT(slotFreeBusyChanged(const QModelIndex&, const QModelIndex&))); slotUpdateConflictLabel( 0 ); //initialize label // confict resolver (should show also resources) connect(mDataModel, SIGNAL(layoutChanged()), SLOT(slotConflictResolverLayoutChanged())); connect(mDataModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int , int)), SLOT(slotConflictResolverAttendeeRemoved(const QModelIndex&,int,int))); connect(mDataModel, SIGNAL(rowsInserted(const QModelIndex&, int , int)), SLOT(slotConflictResolverAttendeeAdded(const QModelIndex&,int,int))); connect(mDataModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), SLOT(slotConflictResolverAttendeeChanged(const QModelIndex&, const QModelIndex&))); //Group substitution connect(filterProxyModel, SIGNAL(layoutChanged()), SLOT(slotGroupSubstitutionLayoutChanged())); connect(filterProxyModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int , int)), SLOT(slotGroupSubstitutionAttendeeRemoved(const QModelIndex&,int,int))); connect(filterProxyModel, SIGNAL(rowsInserted(const QModelIndex&, int , int)), SLOT(slotGroupSubstitutionAttendeeAdded(const QModelIndex&,int,int))); connect(filterProxyModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), SLOT(slotGroupSubstitutionAttendeeChanged(const QModelIndex&, const QModelIndex&))); connect(filterProxyModel, SIGNAL(rowsInserted(const QModelIndex&, int , int)), SLOT(updateCount())); connect(filterProxyModel, SIGNAL(rowsRemoved(const QModelIndex&, int , int)), SLOT(updateCount())); // only update when FullName is changed connect(filterProxyModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), SLOT(updateCount())); connect(filterProxyModel, SIGNAL(layoutChanged()), SLOT(updateCount())); connect(filterProxyModel, SIGNAL(layoutChanged()), SLOT(filterLayoutChanged())); } IncidenceAttendee::~IncidenceAttendee() { } void IncidenceAttendee::load( const KCalCore::Incidence::Ptr &incidence ) { mLoadedIncidence = incidence; if ( iAmOrganizer() || incidence->organizer()->isEmpty() ) { mUi->mOrganizerStack->setCurrentIndex( 0 ); int found = -1; const QString fullOrganizer = incidence->organizer()->fullName(); const QString organizerEmail = incidence->organizer()->email(); for ( int i = 0; i < mUi->mOrganizerCombo->count(); ++i ) { KCalCore::Person::Ptr organizerCandidate = KCalCore::Person::fromFullName( mUi->mOrganizerCombo->itemText( i ) ); if ( organizerCandidate->email() == organizerEmail ) { found = i; mUi->mOrganizerCombo->setCurrentIndex( i ); break; } } if ( found < 0 && !fullOrganizer.isEmpty() ) { mUi->mOrganizerCombo->insertItem( 0, fullOrganizer ); mUi->mOrganizerCombo->setCurrentIndex( 0 ); } mUi->mOrganizerLabel->setVisible( false ); } else { // someone else is the organizer mUi->mOrganizerStack->setCurrentIndex( 1 ); mUi->mOrganizerLabel->setText( incidence->organizer()->fullName() ); mUi->mOrganizerLabel->setVisible( true ); } KCalCore::Attendee::List attendees; foreach(const KCalCore::Attendee::Ptr &a, incidence->attendees()) { attendees << KCalCore::Attendee::Ptr(new KCalCore::Attendee(*a)); } mDataModel->setAttendees(attendees); slotUpdateConflictLabel(0); setActions( incidence->type() ); mWasDirty = false; + QTimer::singleShot(1, this, SLOT(slotEventDurationChanged())); } void IncidenceAttendee::save( const KCalCore::Incidence::Ptr &incidence ) { incidence->clearAttendees(); KCalCore::Attendee::List attendees = mDataModel->attendees(); foreach(KCalCore::Attendee::Ptr attendee, attendees) { Q_ASSERT(attendee); bool skip = false; if (attendee->fullName().isEmpty()) { continue; } if (KPIMUtils::isValidAddress(attendee->email())) { if (KMessageBox::warningYesNo( 0, i18nc("@info", "%1 does not look like a valid email address. " "Are you sure you want to invite this participant?", attendee->email()), i18nc("@title:window", "Invalid Email Address")) != KMessageBox::Yes) { skip = true; } } if (!skip) { incidence->addAttendee(attendee); } } // Must not have an organizer for items without attendees if (!incidence->attendeeCount()) { return; } if ( mUi->mOrganizerStack->currentIndex() == 0 ) { incidence->setOrganizer( mUi->mOrganizerCombo->currentText() ); } else { incidence->setOrganizer( mUi->mOrganizerLabel->text() ); } } bool IncidenceAttendee::isDirty() const { if ( iAmOrganizer() ) { KCalCore::Event tmp; tmp.setOrganizer( mUi->mOrganizerCombo->currentText() ); if ( mLoadedIncidence->organizer()->email() != tmp.organizer()->email() ) { kDebug() << "Organizer changed. Old was " << mLoadedIncidence->organizer()->name() << mLoadedIncidence->organizer()->email() << "; new is " << tmp.organizer()->name() << tmp.organizer()->email(); return true; } } const KCalCore::Attendee::List originalList = mLoadedIncidence->attendees(); KCalCore::Attendee::List newList; foreach(KCalCore::Attendee::Ptr attendee, mDataModel->attendees()) { if (!attendee->fullName().isEmpty()) { newList.append(attendee); } } // The lists sizes *must* be the same. When the organizer is attending the // event as well, he should be in the attendees list as well. if (originalList.size() != newList.size()) { return true; } // Okay, again not the most efficient algorithm, but I'm assuming that in the // bulk of the use cases, the number of attendees is not much higher than 10 or so. foreach(const KCalCore::Attendee::Ptr &attendee, originalList) { bool found = false; for (int i = 0; i < newList.count(); ++i) { if (*(newList[i]) == *attendee) { newList.remove(i); found = true; break; } } if (!found) { // One of the attendees in the original list was not found in the new list. return true; } } return false; } void IncidenceAttendee::changeStatusForMe( KCalCore::Attendee::PartStat stat ) { const IncidenceEditorNG::EditorConfig *config = IncidenceEditorNG::EditorConfig::instance(); Q_ASSERT( config ); for (int i=0;irowCount();i++) { QModelIndex index = mDataModel->index(i, AttendeeTableModel::Email); if ( config->thatIsMe( mDataModel->data(index, Qt::DisplayRole).toString() ) ) { index = mDataModel->index(i, AttendeeTableModel::Status); mDataModel->setData(index, stat); break; } } checkDirtyStatus(); } void IncidenceAttendee::acceptForMe() { changeStatusForMe( KCalCore::Attendee::Accepted ); } void IncidenceAttendee::declineForMe() { changeStatusForMe( KCalCore::Attendee::Declined ); } void IncidenceAttendee::fillOrganizerCombo() { mUi->mOrganizerCombo->clear(); const QStringList lst = IncidenceEditorNG::EditorConfig::instance()->fullEmails(); QStringList uniqueList; for ( QStringList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) { if ( !uniqueList.contains( *it ) ) { uniqueList << *it; } } mUi->mOrganizerCombo->addItems( uniqueList ); } void IncidenceAttendee::checkIfExpansionIsNeeded(const KCalCore::Attendee::Ptr attendee ) { QString fullname = attendee->fullName(); // stop old job KJob *oldJob = mMightBeGroupJobs.key(attendee); if ( oldJob != 0 ) { disconnect(oldJob); oldJob->deleteLater(); mMightBeGroupJobs.remove(oldJob); } mGroupList.remove(attendee); if (!fullname.isEmpty()) { Akonadi::ContactGroupSearchJob *job = new Akonadi::ContactGroupSearchJob(); job->setQuery( Akonadi::ContactGroupSearchJob::Name, fullname); connect( job, SIGNAL(result(KJob*)), this, SLOT(groupSearchResult(KJob*)) ); mMightBeGroupJobs.insert( job, attendee ); } } void IncidenceAttendee::groupSearchResult( KJob *job ) { Akonadi::ContactGroupSearchJob *searchJob = qobject_cast( job ); Q_ASSERT(searchJob); Q_ASSERT(mMightBeGroupJobs.contains(job)); KCalCore::Attendee::Ptr attendee = mMightBeGroupJobs.take(job); const KABC::ContactGroup::List contactGroups = searchJob->contactGroups(); if ( contactGroups.isEmpty() ) { updateGroupExpand(); return; // Nothing todo, probably a normal email address was entered } // TODO: Give the user the possibility to choose a group when there is more than one?! KABC::ContactGroup group = contactGroups.first(); int row = dataModel()->attendees().indexOf(attendee); QModelIndex index = dataModel()->index(row, AttendeeTableModel::CuType); dataModel()->setData(index, KCalCore::Attendee::Group); mGroupList.insert(attendee, group); updateGroupExpand(); } void IncidenceAttendee::updateGroupExpand() { #ifndef KDEPIM_MOBILE_UI mUi->mGroupSubstitution->setEnabled(mGroupList.count() > 0); #endif } void IncidenceAttendee::slotGroupSubstitutionPressed() { foreach (KCalCore::Attendee::Ptr attendee, mGroupList.keys()) { Akonadi::ContactGroupExpandJob *expandJob = new Akonadi::ContactGroupExpandJob( mGroupList.value(attendee), this ); connect( expandJob, SIGNAL(result(KJob*)), this, SLOT(expandResult(KJob*)) ); mExpandGroupJobs.insert(expandJob, attendee); expandJob->start(); } } void IncidenceAttendee::expandResult( KJob *job ) { #ifndef KDEPIM_MOBILE_UI Akonadi::ContactGroupExpandJob *expandJob = qobject_cast( job ); Q_ASSERT( expandJob ); - Q_ASSERT(mExpandGroupJobs.contains(job)); - KCalCore::Attendee::Ptr attendee = mExpandGroupJobs.take(job); - int row = dataModel()->attendees().indexOf(attendee); + bool replace = mExpandGroupJobs.contains(job); + KCalCore::Attendee::Ptr attendee; + int row = dataModel()->attendees().size() - 1; + if (replace){ + attendee = mExpandGroupJobs.take(job); + row = dataModel()->attendees().indexOf(attendee); + dataModel()->removeRow(row); + } else { + attendee = KCalCore::Attendee::Ptr(new KCalCore::Attendee(QString(),QString())); + } - dataModel()->removeRow(row); const KABC::Addressee::List groupMembers = expandJob->contacts(); foreach ( const KABC::Addressee &member, groupMembers ) { KCalCore::Attendee::Ptr newAt(new KCalCore::Attendee(member.realName(), member.preferredEmail(), attendee->RSVP(), attendee->status(), attendee->role(), member.uid())); dataModel()->insertAttendee(row, newAt); } #endif } void IncidenceAttendee::slotSelectAddresses() { QWeakPointer dialog( new Akonadi::EmailAddressSelectionDialog( ) ); dialog.data()->view()->view()->setSelectionMode( QAbstractItemView::ExtendedSelection ); if ( dialog.data()->exec() == QDialog::Accepted ) { Akonadi::EmailAddressSelectionDialog *dialogPtr = dialog.data(); if ( dialogPtr ) { const Akonadi::EmailAddressSelection::List list = dialogPtr->selectedAddresses(); foreach ( const Akonadi::EmailAddressSelection &selection, list ) { if ( selection.item().hasPayload() ) { Akonadi::ContactGroupExpandJob *job = new Akonadi::ContactGroupExpandJob( selection.item().payload(), this ); connect( job, SIGNAL(result(KJob*)), this, SLOT(expandResult(KJob*)) ); job->start(); } else { KABC::Addressee contact; contact.setName( selection.name() ); contact.insertEmail( selection.email() ); if ( selection.item().hasPayload() ) { contact.setUid( selection.item().payload().uid() ); } insertAttendeeFromAddressee( contact ); } } } else { kDebug() << "dialog was already deleted"; } } if ( dialog.data() ) { dialog.data()->deleteLater(); } } void IncidenceEditorNG::IncidenceAttendee::slotSolveConflictPressed() { const int duration = mDateTime->startTime().secsTo( mDateTime->endTime() ); QScopedPointer dialog( new SchedulingDialog( mDateTime->startDate(), mDateTime->startTime(), duration, mConflictResolver, mParentWidget ) ); dialog->slotUpdateIncidenceStartEnd( mDateTime->currentStartDateTime(), mDateTime->currentEndDateTime() ); if ( dialog->exec() == KDialog::Accepted ) { kDebug () << dialog->selectedStartDate() << dialog->selectedStartTime(); mDateTime->setStartDate( dialog->selectedStartDate() ); mDateTime->setStartTime( dialog->selectedStartTime() ); } } void IncidenceAttendee::slotConflictResolverAttendeeChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { #ifndef KDEPIM_MOBILE_UI if (AttendeeTableModel::FullName <= bottomRight.column() && AttendeeTableModel::FullName >= topLeft.column()) { for (int i = topLeft.row(); i <= bottomRight.row(); i++) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email); KCalCore::Attendee::Ptr attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value(); if (mConflictResolver->containsAttendee(attendee)) { mConflictResolver->removeAttendee(attendee); } if (!dataModel()->data(email).toString().isEmpty()) { mConflictResolver->insertAttendee(attendee); } } } checkDirtyStatus(); #endif } void IncidenceAttendee::slotConflictResolverAttendeeAdded(const QModelIndex &index, int first, int last) { for (int i = first; i <= last; i++) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email, index); if (!dataModel()->data(email).toString().isEmpty()) { mConflictResolver->insertAttendee( dataModel()->data(email, AttendeeTableModel::AttendeeRole).value() ); } } checkDirtyStatus(); } void IncidenceAttendee::slotConflictResolverAttendeeRemoved(const QModelIndex &index, int first, int last) { for (int i = first; i <= last; i++) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email, index); if (!dataModel()->data(email).toString().isEmpty()) { mConflictResolver->removeAttendee(dataModel()->data(email, AttendeeTableModel::AttendeeRole).value()); } } checkDirtyStatus(); } void IncidenceAttendee::slotConflictResolverLayoutChanged() { KCalCore::Attendee::List attendees = mDataModel->attendees(); mConflictResolver->clearAttendees(); foreach(KCalCore::Attendee::Ptr attendee, attendees) { if (!attendee->email().isEmpty()) { mConflictResolver->insertAttendee(attendee); } } checkDirtyStatus(); } void IncidenceAttendee::slotFreeBusyAdded(const QModelIndex &parent, int first, int last) { // We are only interested in toplevel changes if (parent.isValid()) { return; } QAbstractItemModel *model = mConflictResolver->model(); for (int i = first; i <= last; i++) { QModelIndex index = model->index(i, 0, parent); const KCalCore::Attendee::Ptr &attendee = model->data(index, FreeBusyItemModel::AttendeeRole).value(); const KCalCore::FreeBusy::Ptr &fb = model->data(index, FreeBusyItemModel::FreeBusyRole).value(); if (attendee) { updateFBStatus(attendee, fb); } } } void IncidenceAttendee::slotFreeBusyChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { // We are only interested in toplevel changes if (topLeft.parent().isValid()) { return; } QAbstractItemModel *model = mConflictResolver->model(); for (int i = topLeft.row(); i <= bottomRight.row(); i++) { QModelIndex index = model->index(i, 0); const KCalCore::Attendee::Ptr &attendee = model->data(index, FreeBusyItemModel::AttendeeRole).value(); const KCalCore::FreeBusy::Ptr &fb = model->data(index, FreeBusyItemModel::FreeBusyRole).value(); if (attendee) { updateFBStatus(attendee, fb); } } } void IncidenceAttendee::updateFBStatus() { QAbstractItemModel *model = mConflictResolver->model(); for (int i = 0; i < model->rowCount(); i++) { QModelIndex index = model->index(i, 0); const KCalCore::Attendee::Ptr &attendee = model->data(index, FreeBusyItemModel::AttendeeRole).value(); const KCalCore::FreeBusy::Ptr &fb = model->data(index, FreeBusyItemModel::FreeBusyRole).value(); if (attendee) { updateFBStatus(attendee, fb); } } } void IncidenceAttendee::updateFBStatus(const KCalCore::Attendee::Ptr &attendee, const KCalCore::FreeBusy::Ptr &fb) { KCalCore::Attendee::List attendees = mDataModel->attendees(); KDateTime startTime = mDateTime->currentStartDateTime(); KDateTime endTime = mDateTime->currentEndDateTime(); QAbstractItemModel *model = mConflictResolver->model(); if (attendees.contains(attendee)) { int row = dataModel()->attendees().indexOf(attendee); QModelIndex attendeeIndex = dataModel()->index(row, AttendeeTableModel::Available); if (fb) { KCalCore::Period::List busyPeriods = fb->busyPeriods(); for ( KCalCore::Period::List::Iterator it = busyPeriods.begin(); it != busyPeriods.end(); ++it ) { // periods started before and laping into the incidence (s < startTime && e >= startTime) // periods starting in the time of incidende (s >= startTime && s <= endTime) if ( ((*it).start() < startTime && (*it).end() > startTime) || ((*it).start() >= startTime && (*it).start() <= endTime) ) { switch (attendee->status()) { case KCalCore::Attendee::Accepted: dataModel()->setData(attendeeIndex, AttendeeTableModel::Accepted); return; default: dataModel()->setData(attendeeIndex, AttendeeTableModel::Busy); return; } } } dataModel()->setData(attendeeIndex, AttendeeTableModel::Free); } else { dataModel()->setData(attendeeIndex, AttendeeTableModel::Unkown); } } } void IncidenceAttendee::slotUpdateConflictLabel( int count ) { kDebug() << "slotUpdateConflictLabel"; if ( attendeeCount() > 0 ) { mUi->mSolveButton->setEnabled( true ); if ( count > 0 ) { QString label = i18ncp( "@label Shows the number of scheduling conflicts", "%1 conflict", "%1 conflicts", count ); mUi->mConflictsLabel->setText( label ); mUi->mConflictsLabel->setVisible( true ); } else { mUi->mConflictsLabel->setVisible( false ); } } else { mUi->mSolveButton->setEnabled( false ); mUi->mConflictsLabel->setVisible( false ); } } void IncidenceAttendee::slotGroupSubstitutionAttendeeChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { if (AttendeeTableModel::FullName <= bottomRight.column() && AttendeeTableModel::FullName >= topLeft.column()) { for (int i = topLeft.row(); i <= bottomRight.row(); i++) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email); KCalCore::Attendee::Ptr attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value(); checkIfExpansionIsNeeded(attendee); } } updateGroupExpand(); } void IncidenceAttendee::slotGroupSubstitutionAttendeeAdded(const QModelIndex &index, int first, int last) { Q_UNUSED(index); for (int i = first; i <= last; i++) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email); KCalCore::Attendee::Ptr attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value(); checkIfExpansionIsNeeded(attendee); } updateGroupExpand(); } void IncidenceAttendee::slotGroupSubstitutionAttendeeRemoved(const QModelIndex &index, int first, int last) { Q_UNUSED(index); for (int i = first; i <= last; i++) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email); KCalCore::Attendee::Ptr attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value(); KJob *job = mMightBeGroupJobs.key(attendee); if (job) { disconnect(job); job->deleteLater(); mMightBeGroupJobs.remove(job); } job = mExpandGroupJobs.key(attendee); if (job) { disconnect(job); job->deleteLater(); mExpandGroupJobs.remove(job); } mGroupList.remove(attendee); } updateGroupExpand(); } void IncidenceAttendee::slotGroupSubstitutionLayoutChanged() { foreach(KJob *job, mMightBeGroupJobs.keys()) { disconnect(job); job->deleteLater(); } foreach(KJob *job, mExpandGroupJobs.keys()) { disconnect(job); job->deleteLater(); } mMightBeGroupJobs.clear(); mExpandGroupJobs.clear(); mGroupList.clear(); #ifndef KDEPIM_MOBILE_UI QAbstractItemModel *model = mUi->mAttendeeTable->model(); if (!model ) { return; } for(int i=0;i< model->rowCount(QModelIndex());i++) { QModelIndex index = model->index(i,AttendeeTableModel::FullName); if (!model->data(index).toString().isEmpty()) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email); KCalCore::Attendee::Ptr attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value(); checkIfExpansionIsNeeded(attendee); } } updateGroupExpand(); #endif } bool IncidenceAttendee::iAmOrganizer() const { if ( mLoadedIncidence ) { const IncidenceEditorNG::EditorConfig *config = IncidenceEditorNG::EditorConfig::instance(); return config->thatIsMe( mLoadedIncidence->organizer()->email() ); } return true; } void IncidenceAttendee::insertAttendeeFromAddressee( const KABC::Addressee &a, int pos/*=-1*/) { const bool sameAsOrganizer = mUi->mOrganizerCombo && KPIMUtils::compareEmail( a.preferredEmail(), mUi->mOrganizerCombo->currentText(), false ); KCalCore::Attendee::PartStat partStat = KCalCore::Attendee::NeedsAction; bool rsvp = true; if ( iAmOrganizer() && sameAsOrganizer ) { partStat = KCalCore::Attendee::Accepted; rsvp = false; } KCalCore::Attendee::Ptr newAt( new KCalCore::Attendee( a.realName(), a.preferredEmail(), rsvp, partStat, KCalCore::Attendee::ReqParticipant, a.uid() ) ); if (pos < 0 ) { pos = dataModel()->rowCount() - 1; } dataModel()->insertAttendee(pos, newAt); } void IncidenceAttendee::slotEventDurationChanged() { const KDateTime start = mDateTime->currentStartDateTime(); const KDateTime end = mDateTime->currentEndDateTime(); if ( start >= end ) { // This can happen, especially for todos. return; } mConflictResolver->setEarliestDateTime( start ); mConflictResolver->setLatestDateTime( end ); updateFBStatus(); } void IncidenceAttendee::slotOrganizerChanged( const QString &newOrganizer ) { if ( KPIMUtils::compareEmail( newOrganizer, mOrganizer, false ) ) { return; } QString name; QString email; bool success = KPIMUtils::extractEmailAddressAndName( newOrganizer, email, name ); if ( !success ) { kWarning() << "Could not extract email address and name"; return; } int currentOrganizerAttendee = -1; int newOrganizerAttendee = -1; for(int i=0; irowCount(); i++) { QModelIndex index = mDataModel->index(i,AttendeeTableModel::FullName); QString fullName = mDataModel->data(index,Qt::DisplayRole).toString(); if ( fullName == mOrganizer ) { currentOrganizerAttendee = i; } if ( fullName == newOrganizer ) { newOrganizerAttendee = i; } } int answer = KMessageBox::No; if ( currentOrganizerAttendee > -1) { answer = KMessageBox::questionYesNo( mParentWidget, i18nc( "@option", "You are changing the organizer of this event. " "Since the organizer is also attending this event, would you " "like to change the corresponding attendee as well?" ) ); } else { answer = KMessageBox::Yes; } if ( answer == KMessageBox::Yes ) { if ( currentOrganizerAttendee > -1 ) { mDataModel->removeRows(currentOrganizerAttendee,1); } if ( newOrganizerAttendee == -1 ) { bool rsvp = !iAmOrganizer(); // if it is the user, don't make him rsvp. KCalCore::Attendee::PartStat status = iAmOrganizer() ? KCalCore::Attendee::Accepted : KCalCore::Attendee::NeedsAction; KCalCore::Attendee::Ptr newAt( new KCalCore::Attendee( name, email, rsvp, status, KCalCore::Attendee::ReqParticipant ) ); mDataModel->insertAttendee(mDataModel->rowCount(), newAt); } } mOrganizer = newOrganizer; } AttendeeTableModel* IncidenceAttendee::dataModel() { return mDataModel; } AttendeeComboBoxDelegate* IncidenceAttendee::responseDelegate() { return mResponseDelegate; } AttendeeComboBoxDelegate* IncidenceAttendee::roleDelegate() { return mRoleDelegate; } AttendeeComboBoxDelegate* IncidenceAttendee::stateDelegate() { return mStateDelegate; } AttendeeLineEditDelegate *IncidenceAttendee::attendeeDelegate() { return mAttendeeDelegate; } void IncidenceAttendee::filterLayoutChanged() { #ifndef KDEPIM_MOBILE_UI QHeaderView* headerView = mUi->mAttendeeTable->horizontalHeader(); headerView->setResizeMode(AttendeeTableModel::Role, QHeaderView::ResizeToContents); headerView->setResizeMode(AttendeeTableModel::FullName, QHeaderView::Stretch); headerView->setResizeMode(AttendeeTableModel::Status, QHeaderView::ResizeToContents); headerView->setResizeMode(AttendeeTableModel::Response, QHeaderView::ResizeToContents); headerView->setSectionHidden(AttendeeTableModel::CuType, true); headerView->setSectionHidden(AttendeeTableModel::Name, true); headerView->setSectionHidden(AttendeeTableModel::Email, true); headerView->setSectionHidden(AttendeeTableModel::Available, true); #endif } void IncidenceAttendee::updateCount() { emit attendeeCountChanged(attendeeCount()); checkDirtyStatus(); } int IncidenceAttendee::attendeeCount() const { #ifndef KDEPIM_MOBILE_UI int c=0; QModelIndex index; QAbstractItemModel *model = mUi->mAttendeeTable->model(); if (!model ) { return 0; } for(int i=0;i< model->rowCount(QModelIndex());i++) { index = model->index(i,AttendeeTableModel::FullName); if (!model->data(index).toString().isEmpty()) { c++; } } return c; #endif return 0; } void IncidenceAttendee::setActions( KCalCore::Incidence::IncidenceType actions ) { mStateDelegate->clear(); if ( actions == KCalCore::Incidence::TypeEvent ) { #ifdef KDEPIM_MOBILE_UI mStateDelegate->addItem( DesktopIcon( "task-attention", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::NeedsAction ) ); mStateDelegate->addItem( DesktopIcon( "task-accepted", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Accepted ) ); mStateDelegate->addItem( DesktopIcon( "task-reject", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Declined ) ); mStateDelegate->addItem( DesktopIcon( "task-attempt", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Tentative ) ); mStateDelegate->addItem( DesktopIcon( "task-delegate", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Delegated ) ); #else mStateDelegate->addItem( SmallIcon( "task-attention" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::NeedsAction ) ); mStateDelegate->addItem( SmallIcon( "task-accepted" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Accepted ) ); mStateDelegate->addItem( SmallIcon( "task-reject" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Declined ) ); mStateDelegate->addItem( SmallIcon( "task-attempt" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Tentative ) ); mStateDelegate->addItem( SmallIcon( "task-delegate" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Delegated ) ); #endif } else { #ifdef KDEPIM_MOBILE_UI mStateDelegate->addItem( DesktopIcon( "task-attention", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::NeedsAction ) ); mStateDelegate->addItem( DesktopIcon( "task-accepted", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Accepted ) ); mStateDelegate->addItem( DesktopIcon( "task-reject", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Declined ) ); mStateDelegate->addItem( DesktopIcon( "task-attempt", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Tentative ) ); mStateDelegate->addItem( DesktopIcon( "task-delegate", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Delegated ) ); mStateDelegate->addItem( DesktopIcon( "task-complete", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Completed ) ); mStateDelegate->addItem( DesktopIcon( "task-ongoing", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::InProcess ) ); #else mStateDelegate->addItem( SmallIcon( "task-attention" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::NeedsAction ) ); mStateDelegate->addItem( SmallIcon( "task-accepted" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Accepted ) ); mStateDelegate->addItem( SmallIcon( "task-reject" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Declined ) ); mStateDelegate->addItem( SmallIcon( "task-attempt" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Tentative ) ); mStateDelegate->addItem( SmallIcon( "task-delegate" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Delegated ) ); mStateDelegate->addItem( SmallIcon( "task-complete" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Completed ) ); mStateDelegate->addItem( SmallIcon( "task-ongoing" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::InProcess ) ); #endif } } void IncidenceAttendee::printDebugInfo() const { kDebug() << "I'm organizer : " << iAmOrganizer(); kDebug() << "Loaded organizer: "<< mLoadedIncidence->organizer()->email(); if ( iAmOrganizer() ) { KCalCore::Event tmp; tmp.setOrganizer( mUi->mOrganizerCombo->currentText() ); kDebug() << "Organizer combo: " << tmp.organizer()->email(); } const KCalCore::Attendee::List originalList = mLoadedIncidence->attendees(); KCalCore::Attendee::List newList; foreach(KCalCore::Attendee::Ptr attendee, mDataModel->attendees()) { if (!attendee->fullName().isEmpty()) { newList.append(attendee); } } // Okay, again not the most efficient algorithm, but I'm assuming that in the // bulk of the use cases, the number of attendees is not much higher than 10 or so. foreach ( const KCalCore::Attendee::Ptr &attendee, originalList ) { bool found = false; for ( int i = 0; i < newList.count(); ++i ) { if ( newList[i] == attendee ) { newList.remove(i); found = true; break; } } if ( !found ) { kDebug() << "Attendee not found: " << attendee->email() << attendee->name() << attendee->status() << attendee->RSVP() << attendee->role() << attendee->uid() << attendee->cuType() << attendee->delegate() << attendee->delegator() << "; we have:"; for ( int i = 0; i < newList.count(); ++i ) { KCalCore::Attendee::Ptr attendee = newList[i]; kDebug() << "Attendee: " << attendee->email() << attendee->name() << attendee->status() << attendee->RSVP() << attendee->role() << attendee->uid() << attendee->cuType() << attendee->delegate() << attendee->delegator(); } return; } } } \ No newline at end of file diff --git a/korganizer/views/collectionview/controller.cpp b/korganizer/views/collectionview/controller.cpp index 251291fb6d..a7b03d6636 100644 --- a/korganizer/views/collectionview/controller.cpp +++ b/korganizer/views/collectionview/controller.cpp @@ -1,476 +1,482 @@ /* Copyright (C) 2014 Christian Mollekopf 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 "controller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include CollectionNode::CollectionNode(ReparentingModel& personModel, const Akonadi::Collection& col) : Node(personModel), mCollection(col), mCheckState(Qt::Unchecked), isSearchNode(false) { } CollectionNode::~CollectionNode() { } bool CollectionNode::operator==(const ReparentingModel::Node &node) const { const CollectionNode *collectionNode = dynamic_cast(&node); if (collectionNode) { return (collectionNode->mCollection == mCollection); } return false; } QVariant CollectionNode::data(int role) const { if (role == Qt::DisplayRole) { QStringList path; Akonadi::Collection c = mCollection; while (c.isValid()) { path.prepend(c.name()); c = c.parentCollection(); } return path.join(QLatin1String("/")); } if (role == Qt::DecorationRole) { if (mCollection.hasAttribute()) { return mCollection.attribute()->icon(); } return QVariant(); } if (role == Qt::CheckStateRole) { if (isSearchNode) { return QVariant(); } return mCheckState; } if (role == Qt::ToolTipRole) { return QString(i18n("Collection: ") + mCollection.name() + QLatin1String(" ") + QString::number(mCollection.id())); } if (role == IsSearchResultRole) { return isSearchNode; } if (role == CollectionRole || role == Akonadi::EntityTreeModel::CollectionRole) { return QVariant::fromValue(mCollection); } if (role == NodeTypeRole) { return CollectionNodeRole; } return QVariant(); } bool CollectionNode::setData(const QVariant& value, int role) { if (role == Qt::CheckStateRole) { mCheckState = static_cast(value.toInt()); emitter.emitEnabled(mCheckState == Qt::Checked, mCollection); return true; } return false; } bool CollectionNode::isDuplicateOf(const QModelIndex& sourceIndex) { return (sourceIndex.data(Akonadi::EntityTreeModel::CollectionIdRole).value() == mCollection.id()); } PersonNode::PersonNode(ReparentingModel& personModel, const Person& person) : Node(personModel), mPerson(person), mCheckState(Qt::Unchecked), isSearchNode(false) { } PersonNode::~PersonNode() { } bool PersonNode::operator==(const Node &node) const { const PersonNode *personNode = dynamic_cast(&node); if (personNode) { return (personNode->mPerson.uid == mPerson.uid); } return false; } void PersonNode::setChecked(bool enabled) { if (enabled) { mCheckState = Qt::Checked; } else { mCheckState = Qt::Unchecked; } } QVariant PersonNode::data(int role) const { if (role == Qt::DisplayRole) { QString name = mPerson.name; + if (!mPerson.group.isEmpty()) { + name = mPerson.group + QLatin1String(" - ") + name; + } if (!mPerson.ou.isEmpty()) { name += QLatin1String(" (") + mPerson.ou + QLatin1String(")"); } return name; } if (role == Qt::DecorationRole) { return KIcon(QLatin1String("meeting-participant")); } if (role == Qt::CheckStateRole) { if (isSearchNode) { return QVariant(); } return mCheckState; } if (role == Qt::ToolTipRole) { QString tooltip = i18n("Person: ") + mPerson.name; + if (!mPerson.group.isEmpty()) { + tooltip += QLatin1String("\n") + i18n("Group: ") + mPerson.group; + } if (!mPerson.mail.isEmpty()) { tooltip += QLatin1String("\n") + i18n("Mail: ") + mPerson.mail; } if (!mPerson.ou.isEmpty()) { tooltip += QLatin1String("\n") + i18n("Organization Unit: ") + mPerson.ou; } return tooltip; } if (role == PersonRole) { return QVariant::fromValue(mPerson); } if (role == IsSearchResultRole) { return isSearchNode; } if (role == NodeTypeRole) { return PersonNodeRole; } if (role == CollectionRole || role == Akonadi::EntityTreeModel::CollectionRole) { return QVariant::fromValue(Akonadi::Collection(mPerson.rootCollection)); } return QVariant(); } bool PersonNode::setData(const QVariant& value, int role) { if (role == Qt::CheckStateRole) { mCheckState = static_cast(value.toInt()); emitter.emitEnabled(mCheckState == Qt::Checked, mPerson); return true; } return false; } bool PersonNode::adopts(const QModelIndex& sourceIndex) { const Akonadi::Collection &parent = sourceIndex.data(Akonadi::EntityTreeModel::ParentCollectionRole).value(); if (parent.id() == mPerson.rootCollection) { return true; } const Akonadi::Collection &col = sourceIndex.data(Akonadi::EntityTreeModel::CollectionRole).value(); // kDebug() << col.displayName(); //FIXME: we need a way to compare the path we get from LDAP to the folder in akonadi. //TODO: get it from the folder attribute if ((col.isValid() && mPerson.folderPaths.contains(col.displayName())) || mPerson.collections.contains(col.id())) { // kDebug() << "reparenting " << col.displayName() << " to " << mPerson.name; return true; } return false; } bool PersonNode::isDuplicateOf(const QModelIndex& sourceIndex) { return (sourceIndex.data(PersonRole).value().uid == mPerson.uid); } void PersonNode::update(const Node::Ptr &node) { mPerson = node.staticCast()->mPerson; } Person PersonNodeManager::person(const QModelIndex &sourceIndex) { Person person; const Akonadi::Collection col = sourceIndex.data(Akonadi::EntityTreeModel::CollectionRole).value(); if (col.isValid()) { CollectionIdentificationAttribute *attr = col.attribute(); if (attr && attr->collectionNamespace() == "usertoplevel") { person.name = col.displayName(); person.mail = QString::fromUtf8(attr->mail()); person.ou = QString::fromUtf8(attr->ou()); person.uid = col.name(); person.rootCollection = col.id(); } } return person; } void PersonNodeManager::checkSourceIndex(const QModelIndex &sourceIndex) { const Person &p = person(sourceIndex); if (p.rootCollection > -1) { model.addNode(ReparentingModel::Node::Ptr(new PersonNode(model, p))); } } void PersonNodeManager::updateSourceIndex(const QModelIndex &sourceIndex) { const Person &p = person(sourceIndex); if (p.rootCollection > -1) { model.updateNode(ReparentingModel::Node::Ptr(new PersonNode(model, p))); } } void PersonNodeManager::checkSourceIndexRemoval(const QModelIndex &sourceIndex) { const Person &p = person(sourceIndex); if (p.rootCollection > -1) { model.removeNode(PersonNode(model, p)); } } Controller::Controller(ReparentingModel* personModel, ReparentingModel* searchModel, QObject* parent) : QObject(parent), mPersonModel(personModel), mSearchModel(searchModel), mCollectionSearchJob(0), mPersonSearchJob(0) { Akonadi::AttributeFactory::registerAttribute(); } void Controller::setSearchString(const QString &searchString) { if (mCollectionSearchJob) { disconnect(mCollectionSearchJob, 0, this, 0); mCollectionSearchJob->kill(KJob::Quietly); mCollectionSearchJob = 0; } if (mPersonSearchJob) { disconnect(mPersonSearchJob, 0, this, 0); mPersonSearchJob->kill(KJob::Quietly); mPersonSearchJob = 0; } //TODO: Delay and abort when results are found mSearchModel->clear(); emit searchIsActive(!searchString.isEmpty()); const bool showAllPersonalFolders = (searchString == QLatin1String("*")); if (searchString.size() < 2 && !showAllPersonalFolders) { emit searching(false); return; } if (!showAllPersonalFolders) { emit searching(true); mPersonSearchJob = new PersonSearchJob(searchString, this); connect(mPersonSearchJob, SIGNAL(personsFound(QList)), this, SLOT(onPersonsFound(QList))); connect(mPersonSearchJob, SIGNAL(personUpdate(Person)), this, SLOT(onPersonUpdate(Person))); connect(mPersonSearchJob, SIGNAL(result(KJob*)), this, SLOT(onPersonsFound(KJob*))); mPersonSearchJob->start(); } mCollectionSearchJob = new CollectionSearchJob(searchString, QStringList() << QLatin1String("text/calendar") << KCalCore::Event::eventMimeType() << KCalCore::Todo::todoMimeType(), this); connect(mCollectionSearchJob, SIGNAL(result(KJob*)), this, SLOT(onCollectionsFound(KJob*))); mCollectionSearchJob->start(); } void Controller::onCollectionsFound(KJob* job) { mCollectionSearchJob = 0; if (!mPersonSearchJob) { emit searching(false); } if (job->error()) { kWarning() << job->errorString(); return; } Q_FOREACH(const Akonadi::Collection &col, static_cast(job)->matchingCollections()) { CollectionNode *collectionNode = new CollectionNode(*mSearchModel, col); collectionNode->isSearchNode = true; //toggled by the checkbox, results in collection getting monitored // connect(&collectionNode->emitter, SIGNAL(enabled(bool, Akonadi::Collection)), this, SLOT(onCollectionEnabled(bool, Akonadi::Collection))); mSearchModel->addNode(ReparentingModel::Node::Ptr(collectionNode)); } } void Controller::onPersonsFound(const QList &persons) { Q_FOREACH(const Person &p, persons) { PersonNode *personNode = new PersonNode(*mSearchModel, p); personNode->isSearchNode = true; //toggled by the checkbox, results in person getting added to main model // connect(&personNode->emitter, SIGNAL(enabled(bool, Person)), this, SLOT(onPersonEnabled(bool, Person))); mSearchModel->addNode(ReparentingModel::Node::Ptr(personNode)); } } void Controller::onPersonUpdate(const Person &person) { PersonNode *personNode = new PersonNode(*mSearchModel, person); personNode->isSearchNode = true; mSearchModel->updateNode(ReparentingModel::Node::Ptr(personNode)); } void Controller::onPersonsFound(KJob* job) { mPersonSearchJob = 0; if (!mCollectionSearchJob) { emit searching(false); } if (job->error()) { kWarning() << job->errorString(); return; } } static Akonadi::EntityTreeModel *findEtm(QAbstractItemModel *model) { QAbstractProxyModel *proxyModel; while (model) { proxyModel = qobject_cast(model); if (proxyModel && proxyModel->sourceModel()) { model = proxyModel->sourceModel(); } else { break; } } return qobject_cast(model); } void Controller::setCollectionState(const Akonadi::Collection &collection, CollectionState collectionState, bool recursive) { //We removed the children first, so the children in the tree are removed before the parents if (recursive) { //We have to include all mimetypes since mimetypes are not available yet (they will be synced once the collectoins are referenced) Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(collection, Akonadi::CollectionFetchJob::Recursive, this); fetchJob->setProperty("collectionState", static_cast(collectionState)); fetchJob->fetchScope().setListFilter(Akonadi::CollectionFetchScope::NoFilter); connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(onPersonCollectionsFetched(KJob*))); } { Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(collection, Akonadi::CollectionFetchJob::Base, this); fetchJob->setProperty("collectionState", static_cast(collectionState)); fetchJob->fetchScope().setListFilter(Akonadi::CollectionFetchScope::NoFilter); connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(onPersonCollectionsFetched(KJob*))); } } void Controller::onPersonCollectionsFetched(KJob* job) { if (job->error()) { kWarning() << "Failed to fetch collections " << job->errorString(); return; } Akonadi::EntityTreeModel *etm = findEtm(mPersonModel); if (!etm) { kWarning() << "Couldn't find etm"; return; } const CollectionState collectionState = static_cast(job->property("collectionState").toInt()); Q_FOREACH(const Akonadi::Collection &col, static_cast(job)->collections()) { // kDebug() << col.displayName() << "do enable " << enabled; Akonadi::Collection modifiedCollection = col; if (collectionState == Enabled) { modifiedCollection.setShouldList(Akonadi::Collection::ListDisplay, true); } if (collectionState == Disabled) { modifiedCollection.setShouldList(Akonadi::Collection::ListDisplay, false); } //HACK: We have no way of getting to the correct session as used by the etm, //and two concurrent jobs end up overwriting the enabled state of each other. etm->setCollectionReferenced(modifiedCollection, collectionState == Referenced); } } void Controller::addPerson(const Person &person) { kDebug() << person.uid << person.name << person.rootCollection; Person p = person; if (person.rootCollection == -1) { Baloo::PIM::CollectionQuery query; query.setNamespace(QStringList() << QLatin1String("usertoplevel")); query.pathMatches(QLatin1String("/Other Users/")+p.uid); query.setLimit(1); Baloo::PIM::ResultIterator it = query.exec(); Akonadi::Collection::List collections; while (it.next()) { collections << Akonadi::Collection(it.id()); } kDebug() << "Found collections " << collections.size() << "for" << p.name; if (collections.size() == 1) { kDebug() << "Set rootCollection=" << collections.at(0).id(); p.rootCollection = collections.at(0).id(); } } PersonNode *personNode = new PersonNode(*mPersonModel, p); personNode->setChecked(true); mPersonModel->addNode(ReparentingModel::Node::Ptr(personNode)); if (p.rootCollection > -1) { setCollectionState(Akonadi::Collection(p.rootCollection), Referenced, true); } else { kDebug() << "well this only a ldap search object without a collection"; //TODO: use freebusy data into calendar } } void Controller::removePerson(const Person &person) { kDebug() << person.uid << person.name << person.rootCollection; mPersonModel->removeNode(PersonNode(*mPersonModel, person)); if (person.rootCollection > -1) { setCollectionState(Akonadi::Collection(person.rootCollection), Disabled, true); } else { kDebug() << "well this only a ldap search object without a collection"; //TODO: delete freebusy data from calendar } } diff --git a/libkdepim/job/person.h b/libkdepim/job/person.h index e11729b92e..32316ac9da 100644 --- a/libkdepim/job/person.h +++ b/libkdepim/job/person.h @@ -1,48 +1,49 @@ /* Copyright (C) 2014 Christian Mollekopf 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 KORG_PERSON_H #define KORG_PERSON_H #include "kdepim_export.h" #include #include struct KDEPIM_EXPORT Person { Person(): rootCollection(-1), updateDisplayName(false) {}; QString name; QString uid; QString ou; QString mail; + QString group; Akonadi::Collection::Id rootCollection; bool updateDisplayName; //FIXME not sure we actually require those two QStringList folderPaths; QList collections; }; Q_DECLARE_METATYPE(Person); #endif diff --git a/libkdepim/job/personsearchjob.cpp b/libkdepim/job/personsearchjob.cpp index 39838a2785..82c8d0b065 100644 --- a/libkdepim/job/personsearchjob.cpp +++ b/libkdepim/job/personsearchjob.cpp @@ -1,253 +1,427 @@ /* Copyright (C) 2014 Christian Mollekopf 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 "personsearchjob.h" +#include #include #include #include #include +#include +#include #include #include #include +class ExpandGroupJob : public QObject +{ + Q_OBJECT +public: + ExpandGroupJob(const KLDAP::LdapObject &groupObject, const KLDAP::LdapServer &server, const QStringList& attributes); + virtual ~ExpandGroupJob(); + + typedef QSharedPointer Ptr; + + virtual void start(); + const QString &groupName() const; + +Q_SIGNALS: + void groupMembers(const QString groupName, const QList &objects); + +private Q_SLOTS: + void groupResult(KLDAP::LdapSearch *ldapSearch, KLDAP::LdapObject object); + void onResult(KLDAP::LdapSearch *); + +private: + void searchNext(KLDAP::LdapSearch *ldapSearch); + KLDAP::LdapConnection mConnection; + KLDAP::LdapAttrValue mGroupMembersDN; + QString mGroupName; + QStringList mAttributes; + QList mResults; +}; + +ExpandGroupJob::ExpandGroupJob(const KLDAP::LdapObject &groupObject, const KLDAP::LdapServer &server, const QStringList &attributes) +: QObject() +, mConnection(server) +, mAttributes(attributes) +, mGroupMembersDN(groupObject.values(QLatin1String("uniqueMember"))) +, mGroupName(QString::fromUtf8(groupObject.value(QLatin1String("cn")))) +{ +} + +ExpandGroupJob::~ExpandGroupJob() +{ + mConnection.close(); +} + +void ExpandGroupJob::start() +{ + mConnection.connect(); + + KLDAP::LdapSearch *search(new KLDAP::LdapSearch(mConnection)); + connect(search, SIGNAL(data(KLDAP::LdapSearch*,KLDAP::LdapObject)), SLOT(groupResult(KLDAP::LdapSearch*,KLDAP::LdapObject))); + connect(search, SIGNAL(result(KLDAP::LdapSearch*)), SLOT(onResult(KLDAP::LdapSearch*))); + searchNext(search); +} + +const QString &ExpandGroupJob::groupName() const +{ + return mGroupName; +} + +void ExpandGroupJob::groupResult(KLDAP::LdapSearch *ldapSearch, KLDAP::LdapObject object) +{ + mResults << object; +} + +void ExpandGroupJob::onResult(KLDAP::LdapSearch *ldapSearch) +{ + if (mGroupMembersDN.isEmpty()) { + emit groupMembers(groupName(), mResults); + } else { + searchNext(ldapSearch); + } +} + +void ExpandGroupJob::searchNext(KLDAP::LdapSearch *ldapSearch) +{ + ldapSearch->search(KLDAP::LdapDN(QString::fromLatin1(mGroupMembersDN.takeFirst())), KLDAP::LdapUrl::Base, QString(), mAttributes); +} + PersonSearchJob::PersonSearchJob(const QString& searchString, QObject* parent) : KJob(parent), mSearchString(searchString) { + QStringList attrs = mLdapSearch.attributes(); + attrs.append(QLatin1String("uniqueMember")); + mLdapSearch.setAttributes(attrs); connect(&mLdapSearch, SIGNAL(searchData(const QList &)), SLOT(onLDAPSearchData(const QList &))); connect(&mLdapSearch, SIGNAL(searchDone()), SLOT(onLDAPSearchDone())); } PersonSearchJob::~PersonSearchJob() { mLdapSearch.cancelSearch(); } bool PersonSearchJob::kill(KJob::KillVerbosity verbosity) { + Q_FOREACH(ExpandGroupJob::Ptr job, mExpandGroupJobs) { + disconnect(job.data()); + } + mExpandGroupJobs.clear(); + mLdapSearch.cancelSearch(); return KJob::kill(verbosity); } void PersonSearchJob::start() { Baloo::PIM::CollectionQuery query; query.setNamespace(QStringList() << QLatin1String("usertoplevel")); query.nameMatches(mSearchString); query.setLimit(200); Baloo::PIM::ResultIterator it = query.exec(); Akonadi::Collection::List collections; while (it.next()) { collections << Akonadi::Collection(it.id()); } kDebug() << "Found persons " << collections.size(); mCollectionSearchDone = false; mLdapSearchDone = false; if (collections.isEmpty()) { //We didn't find anything mCollectionSearchDone = true; } + Q_FOREACH(ExpandGroupJob::Ptr job, mExpandGroupJobs) { + disconnect(job.data()); + } + + mExpandGroupJobs.clear(); + mLdapSearch.startSearch(QLatin1String("*") + mSearchString); if (!collections.isEmpty()) { Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(collections, Akonadi::CollectionFetchJob::Base, this); fetchJob->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All); fetchJob->fetchScope().setListFilter(Akonadi::CollectionFetchScope::NoFilter); connect(fetchJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), this, SLOT(onCollectionsReceived(Akonadi::Collection::List))); connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(onCollectionsFetched(KJob*))); } //The IMAP resource should add a "Person" attribute to the collections in the person namespace, //the ldap query can then be used to update the name (entitydisplayattribute) for the person. } +bool PersonSearchJob::isGroup(KLDAP::LdapObject object) const +{ + const KLDAP::LdapAttrValue objectClasses = object.values(QLatin1String("objectClass")); + if (objectClasses.contains(QLatin1String("groupofuniquenames").latin1()) + || objectClasses.contains(QLatin1String("kolabgroupofuniquenames").latin1())) { + return true; + } + + return false; +} + +void PersonSearchJob::onGroupFound(QString name, QList< KLDAP::LdapObject > members) +{ + Q_ASSERT(mExpandGroupJobs.contains(name)); + ExpandGroupJob::Ptr job = mExpandGroupJobs.take(name); + Q_ASSERT(job); + + QList persons; + Q_FOREACH(const KLDAP::LdapObject &item, members) { + Person person; + person.group = name; + person.name = QString::fromUtf8(item.value(QLatin1String("cn"))); + person.mail = QString::fromUtf8(item.value(QLatin1String("mail"))); + + const int depth = item.dn().depth(); + for ( int i = 0; i < depth; ++i ) { + const QString rdnStr = item.dn().rdnString(i); + if ( rdnStr.startsWith(QLatin1String("ou="), Qt::CaseInsensitive) ) { + person.ou = rdnStr.mid(3); + break; + } + } + const QStringList &parts = person.mail.split(QLatin1Char('@')); + if (parts.count() == 2) { + const QString &uid = parts.at(0); + person.uid = uid; + if (mMatches.contains(uid)) { + const Person &p = mMatches.value(uid); + if (p.mail != person.mail ) { + if (p.rootCollection > -1) { + person.rootCollection = p.rootCollection; + person.updateDisplayName = p.updateDisplayName; + updatePersonCollection(person); + mMatches.insert(uid, person); + } else { + kWarning() << "That should not happen: we found two times persons with the same uid ("<< uid << "), but differnet name:" << p.name << "vs" << person.name; + } + } + } else { //New person found + mMatches.insert(uid, person); + persons << person; + } + } else { + kWarning() << item.dn().toString() << ": invalid email address" << person.mail; + } + } + + if (persons.count() > 0) { + emit personsFound(persons); + } + + onLDAPSearchDone(); +} + void PersonSearchJob::onLDAPSearchData(const QList< KLDAP::LdapResultObject > &list) { QList persons; Q_FOREACH(const KLDAP::LdapResultObject &item, list) { + if (isGroup(item.object)) { + ExpandGroupJob::Ptr job(new ExpandGroupJob(item.object, item.client->server(), mLdapSearch.attributes())); + connect(job.data(), SIGNAL(groupMembers(QString,QList)), SLOT(onGroupFound(QString,QList))); + + if (mExpandGroupJobs.contains(job->groupName())) { + ExpandGroupJob::Ptr j2 = mExpandGroupJobs.take(job->groupName()); + disconnect(j2.data()); + j2->deleteLater(); + } + mExpandGroupJobs.insert(job->groupName(), job); + job->start(); + continue; + } Person person; person.name = QString::fromUtf8(item.object.value(QLatin1String("cn"))); person.mail = QString::fromUtf8(item.object.value(QLatin1String("mail"))); const int depth = item.object.dn().depth(); for ( int i = 0; i < depth; ++i ) { const QString rdnStr = item.object.dn().rdnString(i); if ( rdnStr.startsWith(QLatin1String("ou="), Qt::CaseInsensitive) ) { person.ou = rdnStr.mid(3); break; } } const QStringList &parts = person.mail.split(QLatin1Char('@')); if (parts.count() == 2) { const QString &uid = parts.at(0); person.uid = uid; if (mMatches.contains(uid)) { const Person &p = mMatches.value(uid); if (p.mail != person.mail ) { if (p.rootCollection > -1) { person.rootCollection = p.rootCollection; person.updateDisplayName = p.updateDisplayName; updatePersonCollection(person); mMatches.insert(uid, person); } else { kWarning() << "That should not happen: we found two times persons with the same uid ("<< uid << "), but differnet name:" << p.name << "vs" << person.name; } } } else { //New person found mMatches.insert(uid, person); persons << person; } } else { kWarning() << item.object.dn().toString() << ": invalid email address" << person.mail; } } if (persons.count() > 0) { emit personsFound(persons); } } void PersonSearchJob::onLDAPSearchDone() { + if (mExpandGroupJobs.count() > 0) { + return; + } mLdapSearchDone = true; if (mCollectionSearchDone) { emitResult(); } } void PersonSearchJob::onCollectionsReceived(const Akonadi::Collection::List &list) { QList persons; Q_FOREACH(const Akonadi::Collection &col, list) { Person person; const QString &uid = col.name(); const CollectionIdentificationAttribute *const attr = col.attribute(); const Akonadi::EntityDisplayAttribute *const displayname = col.attribute(); person.rootCollection = col.id(); person.uid = uid; if (attr) { person.ou = QString::fromUtf8(attr->ou()); person.mail = QString::fromUtf8(attr->mail()); person.name = QString::fromUtf8(attr->identifier()); if (!displayname || displayname->displayName().isEmpty() || displayname->displayName() == person.name) { person.updateDisplayName = true; } } else { person.name = col.displayName(); if (!displayname || displayname->displayName().isEmpty()) { person.updateDisplayName = true; } } if (mMatches.contains(uid)) { Person p = mMatches.value(uid); if (p.rootCollection > -1) { //two collection with the same uid ?! kWarning() << "Two collections match to same person" << p.rootCollection << person.rootCollection; } else if (p.mail != person.mail) { p.rootCollection = person.rootCollection; p.updateDisplayName = person.updateDisplayName; updatePersonCollection(p); } else { mMatches.insert(uid, person); emit personUpdate(person); } } else { mMatches.insert(uid, person); persons << person; } } if (persons.count() > 0) { emit personsFound(persons); } } void PersonSearchJob::updatePersonCollection(const Person &person) { Akonadi::Collection c(person.rootCollection); CollectionIdentificationAttribute *identification = c.attribute(Akonadi::Entity::AddIfMissing); if (person.updateDisplayName) { Akonadi::EntityDisplayAttribute *displayname = c.attribute(Akonadi::Entity::AddIfMissing); displayname->setDisplayName(person.name); } //identification->setIdentifier("Other Users/" + person.uid); identification->setIdentifier(person.name.toUtf8()); identification->setName(person.name.toUtf8()); identification->setCollectionNamespace("usertoplevel"); identification->setMail(person.mail.toUtf8()); identification->setOu(person.ou.toUtf8()); Akonadi::CollectionModifyJob *job = new Akonadi::CollectionModifyJob( c, this ); connect(job, SIGNAL(result(KJob*)), this, SLOT(modifyResult(KJob*))); } void PersonSearchJob::onCollectionsFetched(KJob *job) { if (job->error()) { kWarning() << job->errorString(); } mCollectionSearchDone = true; if (mLdapSearchDone) { emitResult(); } } QList PersonSearchJob::matches() const { return mMatches.values(); } void PersonSearchJob::modifyResult(KJob *job) { if (job->error()) { kWarning() << job->errorString(); return; } const Akonadi::CollectionModifyJob *modifyJob = static_cast(job); const Akonadi::Collection &col = modifyJob->collection(); const CollectionIdentificationAttribute *const attr = col.attribute(); const Akonadi::EntityDisplayAttribute *const displayname = col.attribute(); const QString &uid = col.name(); Person &person = mMatches[col.name()]; person.rootCollection = col.id(); person.uid = uid; if (attr) { person.ou = QString::fromUtf8(attr->ou()); person.mail = QString::fromUtf8(attr->mail()); person.name = QString::fromUtf8(attr->identifier()); if (!displayname || displayname->displayName().isEmpty() || displayname->displayName() == person.name) { person.updateDisplayName = true; } } kDebug() << "modified person to" << person.uid << person.name << person.rootCollection; mMatches.insert(person.uid, person); emit personUpdate(person); } +#include "personsearchjob.moc" \ No newline at end of file diff --git a/libkdepim/job/personsearchjob.h b/libkdepim/job/personsearchjob.h index 8924e83745..170e2e9ddc 100644 --- a/libkdepim/job/personsearchjob.h +++ b/libkdepim/job/personsearchjob.h @@ -1,67 +1,77 @@ /* Copyright (C) 2014 Christian Mollekopf 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 KORG_PERSONSEARCHJOB_H #define KORG_PERSONSEARCHJOB_H #include "kdepim_export.h" #include #include #include #include "person.h" +#include + +class ExpandGroupJob; +typedef QSharedPointer ExpandGroupJobPtr; + class KDEPIM_EXPORT PersonSearchJob : public KJob { Q_OBJECT public: explicit PersonSearchJob(const QString &searchString, QObject* parent = 0); virtual ~PersonSearchJob(); virtual void start(); QList matches() const; Q_SIGNALS: void personsFound(const QList &persons); void personUpdate(const Person &person); public Q_SLOTS: bool kill(KillVerbosity verbosity=Quietly); private Q_SLOTS: void onCollectionsReceived(const Akonadi::Collection::List &); void onCollectionsFetched(KJob *); void onLDAPSearchData(const QList &); void onLDAPSearchDone(); void updatePersonCollection(const Person &person); void modifyResult(KJob *job); + void onGroupFound(QString name, QList members); + +private: + bool isGroup(KLDAP::LdapObject object) const; private: QString mSearchString; QHash mMatches; KLDAP::LdapClientSearch mLdapSearch; bool mCollectionSearchDone; bool mLdapSearchDone; + QHash mExpandGroupJobs; }; #endif diff --git a/libksieve/ksieve/scriptbuilder.h b/libksieve/ksieve/scriptbuilder.h index 9d95b36387..7b08ebff3a 100644 --- a/libksieve/ksieve/scriptbuilder.h +++ b/libksieve/ksieve/scriptbuilder.h @@ -1,80 +1,80 @@ /* -*- c++ -*- ksieve/interfaces/scriptbuilder.h This file is part of KSieve, the KDE internet mail/usenet news message filtering library. Copyright (c) 2002-2003 Marc Mutz KSieve is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. KSieve 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 In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #ifndef KSIEVE_KSIEVE_SCRIPTBUILDER_H #define KSIEVE_KSIEVE_SCRIPTBUILDER_H class QString; namespace KSieve { class Error; class ScriptBuilder { public: virtual ~ScriptBuilder() {} virtual void taggedArgument( const QString & tag ) = 0; virtual void stringArgument( const QString & string, bool multiLine, const QString & embeddedHashComment ) = 0; virtual void numberArgument( unsigned long number, char quantifier ) = 0; virtual void stringListArgumentStart() = 0; virtual void stringListEntry( const QString & string, bool multiLine, const QString & embeddedHashComment ) = 0; virtual void stringListArgumentEnd() = 0; - virtual void commandStart( const QString & identifier ) = 0; - virtual void commandEnd() = 0; + virtual void commandStart( const QString & identifier, int lineNumber ) = 0; + virtual void commandEnd(int lineNumber) = 0; virtual void testStart( const QString & identifier ) = 0; virtual void testEnd() = 0; virtual void testListStart() = 0; virtual void testListEnd() = 0; - virtual void blockStart() = 0; - virtual void blockEnd() = 0; + virtual void blockStart(int lineNumber) = 0; + virtual void blockEnd(int lineNumber) = 0; /** A hash comment always includes an implicit lineFeed() at it's end. */ virtual void hashComment( const QString & comment ) = 0; /** Bracket comments inclde explicit lineFeed()s in their content */ virtual void bracketComment( const QString & comment ) = 0; virtual void lineFeed() = 0; virtual void error( const Error & error ) = 0; virtual void finished() = 0; }; } // namespace KSieve #endif diff --git a/libksieve/ksieveui/CMakeLists.txt b/libksieve/ksieveui/CMakeLists.txt index 8f540dc9b8..9a63744432 100644 --- a/libksieve/ksieveui/CMakeLists.txt +++ b/libksieve/ksieveui/CMakeLists.txt @@ -1,199 +1,200 @@ add_definitions( -DQT_NO_CAST_FROM_ASCII ) add_definitions( -DQT_NO_CAST_TO_ASCII ) if(KDEPIM_ENTERPRISE_BUILD) set(USE_GRAPHICAL_SIEVE_EDITOR true) else() set(USE_GRAPHICAL_SIEVE_EDITOR false) endif() configure_file(settings/sieve-editor.kcfg.cmake ${CMAKE_CURRENT_BINARY_DIR}/sieve-editor.kcfg) include_directories(${CMAKE_SOURCE_DIR}/libksieve ${CMAKE_SOURCE_DIR}/pimcommon ${CMAKE_SOURCE_DIR}/pimcommon/sievehighlighter/ ${CMAKE_BINARY_DIR}/pimcommon ${CMAKE_SOURCE_DIR}/pimcommon/util/ ${CMAKE_SOURCE_DIR}/libksieve/ksieveui ${CMAKE_SOURCE_DIR}/libkdepim/ ) add_subdirectory(tests) add_subdirectory(editor/tests) add_subdirectory(managescriptsjob/tests) set(ksieveui_vacation_LIB_SRCS vacation/vacation.cpp vacation/vacationdialog.cpp vacation/vacationhelperjob.cpp vacation/vacationeditwidget.cpp vacation/multiimapvacationdialog.cpp vacation/vacationpagewidget.cpp vacation/vacationwarningwidget.cpp vacation/vacationscriptextractor.cpp vacation/vacationutils.cpp vacation/vacationcreatescriptjob.cpp vacation/vacationcheckjob.cpp vacation/multiimapvacationmanager.cpp vacation/vacationmanager.cpp ) set(ksieveui_editor_LIB_SRCS editor/sieveeditorwidget.cpp editor/sievetextedit.cpp editor/sieveeditor.cpp editor/sievelinenumberarea.cpp editor/sieveinfowidget.cpp editor/sieveeditortextmodewidget.cpp editor/sieveeditorabstractwidget.cpp editor/sieveeditorwarning.cpp editor/sieveeditorparsingmissingfeaturewarning.cpp editor/sieveeditorhelphtmlwidget.cpp editor/sieveeditortabwidget.cpp editor/sieveeditorutil.cpp editor/sieveeditorloadprogressindicator.cpp ) set(ksieveui_autocreatescripts_LIB_SRCS autocreatescripts/sievescriptparsingerrordialog.cpp autocreatescripts/sieveeditorgraphicalmodewidget.cpp autocreatescripts/autocreatescriptdialog.cpp autocreatescripts/sieveconditionwidgetlister.cpp autocreatescripts/sievescriptlistbox.cpp autocreatescripts/sievescriptdescriptiondialog.cpp autocreatescripts/sieveactionwidgetlister.cpp autocreatescripts/sievescriptpage.cpp autocreatescripts/sievescriptblockwidget.cpp autocreatescripts/sievescripttabwidget.cpp autocreatescripts/autocreatescriptutil.cpp autocreatescripts/sieveincludewidget.cpp autocreatescripts/sieveforeverypartwidget.cpp autocreatescripts/sievewidgetpageabstract.cpp autocreatescripts/sieveglobalvariablewidget.cpp autocreatescripts/sieveactions/sieveactionlist.cpp autocreatescripts/sieveactions/sieveaction.cpp autocreatescripts/sieveactions/sieveactiondiscard.cpp autocreatescripts/sieveactions/sieveactionstop.cpp autocreatescripts/sieveactions/sieveactionsetflags.cpp autocreatescripts/sieveactions/sieveactionaddflags.cpp autocreatescripts/sieveactions/sieveactionfileinto.cpp autocreatescripts/sieveactions/sieveactionreject.cpp autocreatescripts/sieveactions/sieveactionkeep.cpp autocreatescripts/sieveactions/sieveactionredirect.cpp autocreatescripts/sieveactions/sieveactionabstractflags.cpp autocreatescripts/sieveactions/sieveactionremoveflags.cpp autocreatescripts/sieveactions/sieveactionnotify.cpp autocreatescripts/sieveactions/sieveactionabstracteditheader.cpp autocreatescripts/sieveactions/sieveactiondeleteheader.cpp autocreatescripts/sieveactions/sieveactionaddheader.cpp autocreatescripts/sieveactions/sieveactionvacation.cpp autocreatescripts/sieveactions/sieveactionenclose.cpp autocreatescripts/sieveactions/sieveactionreplace.cpp autocreatescripts/sieveactions/sieveactionextracttext.cpp autocreatescripts/sieveactions/sieveactionbreak.cpp autocreatescripts/sieveactions/sieveactionconvert.cpp autocreatescripts/sieveactions/sieveactionsetvariable.cpp autocreatescripts/sieveactions/sieveactionreturn.cpp autocreatescripts/sieveactions/widgets/selectflagswidget.cpp autocreatescripts/sieveactions/widgets/selectfileintowidget.cpp autocreatescripts/sieveactions/widgets/addresslineedit.cpp autocreatescripts/sieveactions/widgets/selectaddheaderpositioncombobox.cpp autocreatescripts/sieveactions/widgets/selectimportancecombobox.cpp autocreatescripts/sieveactions/widgets/multilineedit.cpp autocreatescripts/sieveactions/widgets/selectvacationcombobox.cpp autocreatescripts/sieveactions/widgets/selectvariablemodifiercombobox.cpp autocreatescripts/commonwidgets/selectconvertparameterwidget.cpp autocreatescripts/commonwidgets/selectmatchtypecombobox.cpp autocreatescripts/commonwidgets/selectmimetypecombobox.cpp autocreatescripts/commonwidgets/sievehelpbutton.cpp autocreatescripts/sieveconditions/sievecondition.cpp autocreatescripts/sieveconditions/sieveconditionheader.cpp autocreatescripts/sieveconditions/sieveconditionlist.cpp autocreatescripts/sieveconditions/sieveconditionaddress.cpp autocreatescripts/sieveconditions/sieveconditionsize.cpp autocreatescripts/sieveconditions/sieveconditionenvelope.cpp autocreatescripts/sieveconditions/sieveconditionexists.cpp autocreatescripts/sieveconditions/sieveconditiontrue.cpp autocreatescripts/sieveconditions/sieveconditionbody.cpp autocreatescripts/sieveconditions/sieveconditiondate.cpp autocreatescripts/sieveconditions/sieveconditioncurrentdate.cpp autocreatescripts/sieveconditions/sieveconditionmailboxexists.cpp autocreatescripts/sieveconditions/sieveconditionspamtest.cpp autocreatescripts/sieveconditions/sieveconditionvirustest.cpp autocreatescripts/sieveconditions/sieveconditionihave.cpp autocreatescripts/sieveconditions/sieveconditionfalse.cpp autocreatescripts/sieveconditions/sieveconditionenvironment.cpp autocreatescripts/sieveconditions/sieveconditionhasflag.cpp autocreatescripts/sieveconditions/sieveconditionmetadata.cpp autocreatescripts/sieveconditions/sieveconditionconvert.cpp autocreatescripts/sieveconditions/sieveconditionmetadataexists.cpp autocreatescripts/sieveconditions/sieveconditionservermetadata.cpp autocreatescripts/sieveconditions/sieveconditionservermetadataexists.cpp autocreatescripts/sieveconditions/widgets/selectdatewidget.cpp autocreatescripts/sieveconditions/widgets/selectaddresspartcombobox.cpp autocreatescripts/sieveconditions/widgets/selectheadertypecombobox.cpp autocreatescripts/sieveconditions/widgets/selectbodytypewidget.cpp autocreatescripts/sieveconditions/widgets/selectrelationalmatchtype.cpp autocreatescripts/sieveconditions/widgets/selectcomparatorcombobox.cpp autocreatescripts/sieveconditions/widgets/selectmimecombobox.cpp autocreatescripts/sieveconditions/widgets/selectsizetypecombobox.cpp autocreatescripts/sieveconditions/widgets/selectsizewidget.cpp ) set(ksieveui_scriptsparsing_LIB_SRCS scriptsparsing/xmlprintingscriptbuilder.cpp scriptsparsing/parsingresultdialog.cpp scriptsparsing/xmlprintingsyntaxhighlighter.cpp scriptsparsing/parsingutil.cpp ) set(ksieveui_LIB_SRCS managesievescriptsdialog.cpp widgets/sievetreewidgetitem.cpp widgets/managesievetreeview.cpp widgets/managesievewidget.cpp debug/sievedebugdialog.cpp util/util.cpp + managescriptsjob/checkkep14supportjob.cpp managescriptsjob/generateglobalscriptjob.cpp managescriptsjob/parseuserscriptjob.cpp templates/sievetemplatewidget.cpp templates/sievedefaulttemplate.cpp templates/sievetemplateeditdialog.cpp ${ksieveui_scriptsparsing_LIB_SRCS} ${ksieveui_vacation_LIB_SRCS} ${ksieveui_editor_LIB_SRCS} ${ksieveui_autocreatescripts_LIB_SRCS} ) kde4_add_kcfg_files(ksieveui_LIB_SRCS settings/sieve-vacation.kcfgc settings/sieve-editor.kcfgc) kde4_add_library(ksieveui ${LIBRARY_TYPE} ${ksieveui_LIB_SRCS}) target_link_libraries(ksieveui kmanagesieve ksieve pimcommon kdepim ${KDEPIMLIBS_AKONADI_LIBS} ${KDEPIMLIBS_KMIME_LIBS} ${KDEPIMLIBS_KPIMIDENTITIES_LIBS} ${KDE4_KIO_LIBS} ${KDE4_KDECORE_LIBS} ${QT_QTWEBKIT_LIBRARY} ${KDE4_SOLID_LIBS} ) set_target_properties( ksieveui PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) install(TARGETS ksieveui ${INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES data/ksieve_script.knsrc DESTINATION ${CONFIG_INSTALL_DIR} ) install(DIRECTORY data/scripts/ DESTINATION ${DATA_INSTALL_DIR}/sieve/scripts/ ) add_subdirectory(autocreatescripts/tests) add_subdirectory(scriptsparsing/tests) add_subdirectory(vacation/tests) diff --git a/libksieve/ksieveui/managescriptsjob/checkkep14supportjob.cpp b/libksieve/ksieveui/managescriptsjob/checkkep14supportjob.cpp new file mode 100644 index 0000000000..d094e1e3da --- /dev/null +++ b/libksieve/ksieveui/managescriptsjob/checkkep14supportjob.cpp @@ -0,0 +1,93 @@ +/* + Copyright (c) 2015 Sandro KnauĂź + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + 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 +*/ + +#include "checkkep14supportjob.h" +#include +#include + +#include +#include +#include + +using namespace KSieveUi; + +CheckKep14SupportJob::CheckKep14SupportJob(QObject *parent) + : QObject(parent), + mSieveJob(0) +{ + +} + +CheckKep14SupportJob::~CheckKep14SupportJob() +{ + +} + +void CheckKep14SupportJob::start() +{ + if (mUrl.isEmpty()) { + qDebug() << " server url is empty"; + deleteLater(); + return; + } + mSieveJob = KManageSieve::SieveJob::list(mUrl); + connect(mSieveJob, SIGNAL(gotList(KManageSieve::SieveJob*,bool,QStringList,QString)), + this, SLOT(slotCheckKep14Support(KManageSieve::SieveJob*,bool,QStringList,QString))); +} + +void CheckKep14SupportJob::setServerUrl(const KUrl &url) +{ + mUrl = url; +} + +KUrl CheckKep14SupportJob::serverUrl() +{ + return mUrl; +} + +void CheckKep14SupportJob::setServerName(const QString &name) +{ + mServerName = name; +} + +QString CheckKep14SupportJob::serverName() +{ + return mServerName; +} + + +QStringList CheckKep14SupportJob::availableScripts() +{ + return mAvailableScripts; +} + +bool CheckKep14SupportJob::hasKep14Support() +{ + return mKep14Support; +} + +void CheckKep14SupportJob::slotCheckKep14Support(KManageSieve::SieveJob *job, bool success, const QStringList &availableScripts, const QString &activeScript) +{ + if (!success) { + emit result(this, false); + return; + } + + mKep14Support = Util::hasKep14Support(job->sieveCapabilities(), availableScripts, activeScript); + mAvailableScripts = availableScripts; + emit result(this, true); +} diff --git a/libksieve/ksieveui/vacation/vacationcreatescriptjob.h b/libksieve/ksieveui/managescriptsjob/checkkep14supportjob.h similarity index 54% copy from libksieve/ksieveui/vacation/vacationcreatescriptjob.h copy to libksieve/ksieveui/managescriptsjob/checkkep14supportjob.h index c23584e78a..8b189ef822 100644 --- a/libksieve/ksieveui/vacation/vacationcreatescriptjob.h +++ b/libksieve/ksieveui/managescriptsjob/checkkep14supportjob.h @@ -1,65 +1,65 @@ /* - Copyright (c) 2013, 2014 Montel Laurent + Copyright (c) 2015 Sandro KnauĂź This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ -#ifndef VACATIONCREATESCRIPTJOB_H -#define VACATIONCREATESCRIPTJOB_H +#ifndef CHECKKEP14SUPPORTJOB_H +#define CHECKKEP14SUPPORTJOB_H #include +#include #include "ksieveui_export.h" #include namespace KManageSieve { class SieveJob; } namespace KSieveUi { -class KSIEVEUI_EXPORT VacationCreateScriptJob : public QObject +class KSIEVEUI_EXPORT CheckKep14SupportJob : public QObject { Q_OBJECT public: - explicit VacationCreateScriptJob(QObject *parent=0); - ~VacationCreateScriptJob(); + explicit CheckKep14SupportJob(QObject *parent=0); + ~CheckKep14SupportJob(); void start(); void setServerUrl(const KUrl &url); - void setScript(const QString &script); - void setServerName(const QString &servername); - void setStatus(bool activate, bool wasActive); + void setServerName(const QString &name); + QString serverName(); -Q_SIGNALS: - void result(bool); - void scriptActive(bool activated, const QString &serverName); + QStringList availableScripts(); + bool hasKep14Support(); + KUrl serverUrl(); -private slots: - void slotPutActiveResult(KManageSieve::SieveJob *job, bool success); - void slotPutInactiveResult(KManageSieve::SieveJob *job, bool success); +Q_SIGNALS: + void result(CheckKep14SupportJob*, bool); private: - void handlePutResult(KManageSieve::SieveJob *, bool success, bool activated); KUrl mUrl; - QString mScript; - QString mServerName; - bool mActivate; - bool mWasActive; KManageSieve::SieveJob *mSieveJob; + QStringList mAvailableScripts; + bool mKep14Support; + QString mServerName; + +private slots: + void slotCheckKep14Support(KManageSieve::SieveJob *job, bool success, const QStringList &availableScripts, const QString &activeScript); }; } -#endif // VACATIONCREATESCRIPTJOB_H +#endif // CHECKKEP14SUPPORTJOB_H diff --git a/libksieve/ksieveui/managescriptsjob/generateglobalscriptjob.cpp b/libksieve/ksieveui/managescriptsjob/generateglobalscriptjob.cpp index 4f75ba258f..9a08a8453e 100644 --- a/libksieve/ksieveui/managescriptsjob/generateglobalscriptjob.cpp +++ b/libksieve/ksieveui/managescriptsjob/generateglobalscriptjob.cpp @@ -1,135 +1,146 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #include "generateglobalscriptjob.h" #include "libksieve/kmanagesieve/sievejob.h" #include using namespace KSieveUi; GenerateGlobalScriptJob::GenerateGlobalScriptJob(const KUrl &url, QObject *parent) : QObject(parent), mCurrentUrl(url), - mMasterjob(0), + mMasterJob(0), mUserJob(0) { } GenerateGlobalScriptJob::~GenerateGlobalScriptJob() { - if (mMasterjob) - mMasterjob->kill(); - if (mUserJob) + kill(); +} + +void GenerateGlobalScriptJob::kill() +{ + if (mMasterJob) { + mMasterJob->kill(); + } + mMasterJob = 0; + + if (mUserJob) { mUserJob->kill(); + } + mUserJob = 0; } + void GenerateGlobalScriptJob::addUserActiveScripts(const QStringList &lstScript) { mListUserActiveScripts = lstScript; } void GenerateGlobalScriptJob::start() { if (mCurrentUrl.isEmpty()) { Q_EMIT error(i18n("Path is not specified.")); return; } - writeMasterScript(); + writeUserScript(); } void GenerateGlobalScriptJob::writeMasterScript() { const QString masterScript = QLatin1String("# MASTER\n" "#\n" "# This file is authoritative for your system and MUST BE KEPT ACTIVE.\n" "#\n" "# Altering it is likely to render your account dysfunctional and may\n" "# be violating your organizational or corporate policies.\n" "# \n" "# For more information on the mechanism and the conventions behind\n" "# this script, see http://wiki.kolab.org/KEP:14\n" "#\n" "\n" "require [\"include\"];\n" "\n" "# OPTIONAL: Includes for all or a group of users\n" "# include :global \"all-users\";\n" "# include :global \"this-group-of-users\";\n" "\n" "# The script maintained by the general management system\n" "include :personal :optional \"MANAGEMENT\";\n" "\n" "# The script(s) maintained by one or more editors available to the user\n" "include :personal :optional \"USER\";\n"); KUrl url(mCurrentUrl); url.setFileName(QLatin1String("MASTER")); - mMasterjob = KManageSieve::SieveJob::put(url, masterScript, true, true ); - connect( mMasterjob, SIGNAL(result(KManageSieve::SieveJob*,bool,QString,bool)), + mMasterJob = KManageSieve::SieveJob::put(url, masterScript, true, true ); + connect( mMasterJob, SIGNAL(result(KManageSieve::SieveJob*,bool,QString,bool)), this, SLOT(slotPutMasterResult(KManageSieve::SieveJob*,bool)) ); } void GenerateGlobalScriptJob::slotPutMasterResult( KManageSieve::SieveJob *, bool success ) { if (!success) { Q_EMIT error(i18n("Error when we wrote \"MASTER\" script on server.")); return; } - mMasterjob = 0; + mMasterJob = 0; writeUserScript(); } void GenerateGlobalScriptJob::writeUserScript() { QString userScript = QLatin1String("# USER Management Script\n" "#\n" "# This script includes the various active sieve scripts\n" "# it is AUTOMATICALLY GENERATED. DO NOT EDIT MANUALLY!\n" "# \n" "# For more information, see http://wiki.kolab.org/KEP:14#USER\n" "#\n" "\n" "require [\"include\"];\n"); Q_FOREACH (const QString &activeScript, mListUserActiveScripts) { - userScript += QString::fromLatin1("\ninclude :personal \"%1\"").arg(activeScript); + userScript += QString::fromLatin1("\ninclude :personal \"%1\";").arg(activeScript); } KUrl url(mCurrentUrl); url.setFileName(QLatin1String("USER")); mUserJob = KManageSieve::SieveJob::put(url, userScript, false, false ); connect( mUserJob, SIGNAL(result(KManageSieve::SieveJob*,bool,QString,bool)), this, SLOT(slotPutUserResult(KManageSieve::SieveJob*,bool)) ); } void GenerateGlobalScriptJob::slotPutUserResult( KManageSieve::SieveJob *, bool success ) { mUserJob = 0; if (!success) { Q_EMIT error(i18n("Error when we wrote \"User\" script on server.")); return; } disableAllOtherScripts(); } void GenerateGlobalScriptJob::disableAllOtherScripts() { //TODO Q_EMIT success(); } diff --git a/libksieve/ksieveui/managescriptsjob/generateglobalscriptjob.h b/libksieve/ksieveui/managescriptsjob/generateglobalscriptjob.h index c09471e29f..6dc8c1b0e4 100644 --- a/libksieve/ksieveui/managescriptsjob/generateglobalscriptjob.h +++ b/libksieve/ksieveui/managescriptsjob/generateglobalscriptjob.h @@ -1,58 +1,59 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #ifndef GENERATEGLOBALSCRIPTJOB_H #define GENERATEGLOBALSCRIPTJOB_H #include #include #include namespace KManageSieve { class SieveJob; } namespace KSieveUi { class GenerateGlobalScriptJob : public QObject { Q_OBJECT public: explicit GenerateGlobalScriptJob(const KUrl &url, QObject *parent=0); ~GenerateGlobalScriptJob(); void start(); + void kill(); void addUserActiveScripts(const QStringList &lstScript); Q_SIGNALS: void success(); void error(const QString &msgError); private Q_SLOTS: void slotPutMasterResult( KManageSieve::SieveJob *, bool success ); void slotPutUserResult( KManageSieve::SieveJob *, bool success ); private: void disableAllOtherScripts(); void writeMasterScript(); void writeUserScript(); QStringList mListUserActiveScripts; KUrl mCurrentUrl; - KManageSieve::SieveJob *mMasterjob; + KManageSieve::SieveJob *mMasterJob; KManageSieve::SieveJob *mUserJob; }; } #endif // GENERATEGLOBALSCRIPTJOB_H diff --git a/libksieve/ksieveui/managescriptsjob/parseuserscriptjob.cpp b/libksieve/ksieveui/managescriptsjob/parseuserscriptjob.cpp index ef06c50d11..02de7a4c08 100644 --- a/libksieve/ksieveui/managescriptsjob/parseuserscriptjob.cpp +++ b/libksieve/ksieveui/managescriptsjob/parseuserscriptjob.cpp @@ -1,128 +1,159 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #include "parseuserscriptjob.h" #include "ksieveui/scriptsparsing/parsingutil.h" #include #include using namespace KSieveUi; -ParseUserScriptJob::ParseUserScriptJob(QObject *parent) +ParseUserScriptJob::ParseUserScriptJob(const KUrl &url, QObject *parent) : QObject(parent), mSieveJob(0) + , mCurrentUrl(url) { } ParseUserScriptJob::~ParseUserScriptJob() { - if ( mSieveJob ) - mSieveJob->kill(); - mSieveJob = 0; + kill(); } -void ParseUserScriptJob::scriptUrl(const KUrl &url) +KUrl ParseUserScriptJob::scriptUrl() const { - mCurrentUrl = url; + return mCurrentUrl; } void ParseUserScriptJob::start() { if (mCurrentUrl.isEmpty()) { - Q_EMIT error(i18n("Path is not specified.")); + emitError(i18n("Path is not specified.")); return; } if ( mSieveJob ) mSieveJob->kill(); + mActiveScripts = QStringList(); + mError = QString(); mSieveJob = KManageSieve::SieveJob::get( mCurrentUrl ); connect( mSieveJob, SIGNAL(result(KManageSieve::SieveJob*,bool,QString,bool)), this, SLOT(slotGetResult(KManageSieve::SieveJob*,bool,QString,bool)) ); } void ParseUserScriptJob::slotGetResult( KManageSieve::SieveJob *, bool, const QString & script, bool ) { mSieveJob = 0; if (script.isEmpty()) { - Q_EMIT error(i18n("Script is empty.")); + emitError(i18n("Script is empty.")); return; } bool result; const QStringList lst = parsescript(script, result); if (result) - Q_EMIT success(lst); + emitSuccess(lst); else - Q_EMIT error(i18n("Script parsing error")); + emitError(i18n("Script parsing error")); +} + +void ParseUserScriptJob::emitError(const QString &msgError) +{ + mError = msgError; + emit finished(this); +} + +void ParseUserScriptJob::emitSuccess(const QStringList &activeScriptList) +{ + mActiveScripts = activeScriptList; + emit finished(this); } QStringList ParseUserScriptJob::parsescript(const QString &script, bool &result) { QStringList lst; const QDomDocument doc = ParsingUtil::parseScript(script, result); if (result) { lst = extractActiveScript(doc); } return lst; } +QStringList ParseUserScriptJob::activeScriptList() const +{ + return mActiveScripts; +} + +QString ParseUserScriptJob::error() const +{ + return mError; +} + +void ParseUserScriptJob::kill() +{ + if ( mSieveJob ) + mSieveJob->kill(); + mSieveJob = 0; +} + + QStringList ParseUserScriptJob::extractActiveScript(const QDomDocument &doc) { QStringList lstScript; QDomElement docElem = doc.documentElement(); QDomNode n = docElem.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { const QString tagName = e.tagName(); if (tagName == QLatin1String("action")) { if (e.hasAttribute(QLatin1String("name"))) { const QString actionName = e.attribute(QLatin1String("name")); if (actionName == QLatin1String("include")) { //Load includes const QString str = loadInclude(e); if (!str.isEmpty()) { if (!lstScript.contains(str)) { lstScript.append(str); } } } } } } n = n.nextSibling(); } return lstScript; } QString ParseUserScriptJob::loadInclude(const QDomElement &element) { QString scriptName; QDomNode node = element.firstChild(); while (!node.isNull()) { QDomElement e = node.toElement(); if (!e.isNull()) { const QString tagName = e.tagName(); if (tagName == QLatin1String("str")) { scriptName = e.text(); } } node = node.nextSibling(); } return scriptName; } diff --git a/libksieve/ksieveui/managescriptsjob/parseuserscriptjob.h b/libksieve/ksieveui/managescriptsjob/parseuserscriptjob.h index 165a06268f..435ec65685 100644 --- a/libksieve/ksieveui/managescriptsjob/parseuserscriptjob.h +++ b/libksieve/ksieveui/managescriptsjob/parseuserscriptjob.h @@ -1,59 +1,68 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #ifndef PARSEUSERSCRIPTJOB_H #define PARSEUSERSCRIPTJOB_H #include +#include #include #include "ksieveui_export.h" class QDomDocument; class QDomElement; namespace KManageSieve { class SieveJob; } namespace KSieveUi { class KSIEVEUI_EXPORT ParseUserScriptJob : public QObject { Q_OBJECT + + friend class ParseUserJobTest; public: - explicit ParseUserScriptJob(QObject *parent=0); + explicit ParseUserScriptJob(const KUrl &url,QObject *parent=0); ~ParseUserScriptJob(); void start(); - void scriptUrl(const KUrl &url); - static QStringList parsescript(const QString &script, bool &result); + KUrl scriptUrl() const; + QStringList activeScriptList() const; + QString error() const; + void kill(); private Q_SLOTS: void slotGetResult( KManageSieve::SieveJob *, bool, const QString &, bool ); Q_SIGNALS: - void success(const QStringList &activeScriptList); - void error(const QString &msgError); + void finished(ParseUserScriptJob* job); private: + void emitSuccess(const QStringList &activeScriptList); + void emitError(const QString &msgError); static QString loadInclude(const QDomElement &element); static QStringList extractActiveScript(const QDomDocument &doc); + static QStringList parsescript(const QString &script, bool &result); KUrl mCurrentUrl; KManageSieve::SieveJob *mSieveJob; + QStringList mActiveScripts; + QString mError; }; } #endif // PARSEUSERSCRIPTJOB_H diff --git a/libksieve/ksieveui/managescriptsjob/tests/parseuserjobtest.cpp b/libksieve/ksieveui/managescriptsjob/tests/parseuserjobtest.cpp index 2f39ffa8ad..c699e938ef 100644 --- a/libksieve/ksieveui/managescriptsjob/tests/parseuserjobtest.cpp +++ b/libksieve/ksieveui/managescriptsjob/tests/parseuserjobtest.cpp @@ -1,105 +1,106 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #include "parseuserjobtest.h" #include "ksieveui/managescriptsjob/parseuserscriptjob.h" #include +using namespace KSieveUi; -QTEST_KDEMAIN( ParseUserTest, NoGUI ) +QTEST_KDEMAIN( ParseUserJobTest, NoGUI ) -void ParseUserTest::testParseEmptyUserJob() +void ParseUserJobTest::testParseEmptyUserJob() { const QString script; bool result; - const QStringList lst = KSieveUi::ParseUserScriptJob::parsescript(script, result); + const QStringList lst = ParseUserScriptJob::parsescript(script, result); QCOMPARE(lst.count(), 0); QCOMPARE(result, true); } -void ParseUserTest::testParseUserTwoActiveScriptJob() +void ParseUserJobTest::testParseUserTwoActiveScriptJob() { const QString script = QLatin1String("# USER Management Script\n" "#\n" "# This script includes the various active sieve scripts\n" "# it is AUTOMATICALLY GENERATED. DO NOT EDIT MANUALLY!\n" "# \n" "# For more information, see http://wiki.kolab.org/KEP:14#USER\n" "#\n" "\n" "require [\"include\"];\n" "include :personal \"file1\";\n" "include :personal \"file2\";\n"); bool result; - const QStringList lst = KSieveUi::ParseUserScriptJob::parsescript(script, result); + const QStringList lst = ParseUserScriptJob::parsescript(script, result); QCOMPARE(lst.count(), 2); QCOMPARE(result, true); } -void ParseUserTest::testParseUserNoActiveScriptJob() +void ParseUserJobTest::testParseUserNoActiveScriptJob() { const QString script = QLatin1String("# USER Management Script\n" "#\n" "# This script includes the various active sieve scripts\n" "# it is AUTOMATICALLY GENERATED. DO NOT EDIT MANUALLY!\n" "# \n" "# For more information, see http://wiki.kolab.org/KEP:14#USER\n" "#\n" "\n" "require [\"include\"];\n"); bool result; - const QStringList lst = KSieveUi::ParseUserScriptJob::parsescript(script, result); + const QStringList lst = ParseUserScriptJob::parsescript(script, result); QCOMPARE(lst.count(), 0); QCOMPARE(result, true); } -void ParseUserTest::testParseUserDuplicateActiveScriptJob() +void ParseUserJobTest::testParseUserDuplicateActiveScriptJob() { const QString script = QLatin1String("# USER Management Script\n" "#\n" "# This script includes the various active sieve scripts\n" "# it is AUTOMATICALLY GENERATED. DO NOT EDIT MANUALLY!\n" "# \n" "# For more information, see http://wiki.kolab.org/KEP:14#USER\n" "#\n" "\n" "require [\"include\"];\n" "include :personal \"file1\";\n" "include :personal \"file1\";\n"); bool result; - const QStringList lst = KSieveUi::ParseUserScriptJob::parsescript(script, result); + const QStringList lst = ParseUserScriptJob::parsescript(script, result); QCOMPARE(lst.count(), 1); QCOMPARE(result, true); } -void ParseUserTest::testParseUserErrorScriptJob() +void ParseUserJobTest::testParseUserErrorScriptJob() { const QString script = QLatin1String("# USER Management Script\n" "#\n" "# This script includes the various active sieve scripts\n" "# it is AUTOMATICALLY GENERATED. DO NOT EDIT MANUALLY!\n" "# \n" "# For more information, see http://wiki.kolab.org/KEP:14#USER\n" "#\n" "\n" "errorscript\n"); bool result; - const QStringList lst = KSieveUi::ParseUserScriptJob::parsescript(script, result); + const QStringList lst = ParseUserScriptJob::parsescript(script, result); QCOMPARE(lst.count(), 0); QCOMPARE(result, false); } diff --git a/libksieve/ksieveui/managescriptsjob/tests/parseuserjobtest.h b/libksieve/ksieveui/managescriptsjob/tests/parseuserjobtest.h index 3a62665ce5..7806c218b4 100644 --- a/libksieve/ksieveui/managescriptsjob/tests/parseuserjobtest.h +++ b/libksieve/ksieveui/managescriptsjob/tests/parseuserjobtest.h @@ -1,33 +1,35 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #ifndef PARSEUSERJOBTEST_H #define PARSEUSERJOBTEST_H #include -class ParseUserTest : public QObject + +namespace KSieveUi { +class ParseUserJobTest : public QObject { Q_OBJECT private Q_SLOTS: void testParseEmptyUserJob(); void testParseUserTwoActiveScriptJob(); void testParseUserNoActiveScriptJob(); void testParseUserDuplicateActiveScriptJob(); void testParseUserErrorScriptJob(); }; - +} #endif // PARSEUSERJOBTEST_H diff --git a/libksieve/ksieveui/scriptsparsing/xmlprintingscriptbuilder.cpp b/libksieve/ksieveui/scriptsparsing/xmlprintingscriptbuilder.cpp index 0fa9c34144..65c382c5b8 100644 --- a/libksieve/ksieveui/scriptsparsing/xmlprintingscriptbuilder.cpp +++ b/libksieve/ksieveui/scriptsparsing/xmlprintingscriptbuilder.cpp @@ -1,215 +1,215 @@ /* Copyright (c) 2012, 2013 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #include "xmlprintingscriptbuilder.h" #include using KSieve::Parser; #include #include using namespace KSieveUi; XMLPrintingScriptBuilder::XMLPrintingScriptBuilder() : KSieve::ScriptBuilder(), mIsAction(false) { write( QLatin1String("") ); write( QLatin1String("") ); } void XMLPrintingScriptBuilder::write( const QString &msg ) { mResult += msg; } void XMLPrintingScriptBuilder::write( const QString & key, const QString & value ) { if ( value.isEmpty() ) { write( QString::fromLatin1("<%1>").arg(key) ); return; } write( QString::fromLatin1("<%1>").arg(key) ); write( value ); write( QString::fromLatin1("").arg(key) ); } void XMLPrintingScriptBuilder::write( const QString & key, const QString &attribute, const QString & value ) { if ( value.isEmpty() ) { write( QString::fromLatin1("<%1/>").arg(key) ); return; } if (attribute.isEmpty()) write( QString::fromLatin1("<%1>").arg(key) ); else write( QString::fromLatin1("<%1 %2>").arg(key).arg(attribute) ); write( value ); write( QString::fromLatin1("").arg(key) ); } QString XMLPrintingScriptBuilder::result() const { return mResult; } QString XMLPrintingScriptBuilder::error() const { return mError; } bool XMLPrintingScriptBuilder::hasError() const { return !mError.isEmpty(); } void XMLPrintingScriptBuilder::clear() { mResult.clear(); mError.clear(); } QDomDocument XMLPrintingScriptBuilder::toDom() const { QString errorMsg; int errorRow; int errorCol; QDomDocument doc; if ( !doc.setContent( mResult, &errorMsg, &errorRow, &errorCol ) ) { kDebug() << "Unable to load document.Parse error in line " << errorRow << ", col " << errorCol << ": " << errorMsg; kDebug()<<" mResult"< This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #ifndef KSIEVE_KSIEVEUI_XMLPRINTINGSCRIPTBUILDER_H #define KSIEVE_KSIEVEUI_XMLPRINTINGSCRIPTBUILDER_H #include "ksieveui_export.h" #include #include namespace KSieveUi { class KSIEVEUI_EXPORT XMLPrintingScriptBuilder : public KSieve::ScriptBuilder { public: explicit XMLPrintingScriptBuilder(); ~XMLPrintingScriptBuilder(); void taggedArgument( const QString &tag ); void stringArgument( const QString &string, bool multiLine, const QString & /*fixme*/ ); void numberArgument( unsigned long number, char quantifier ); - void commandStart( const QString &identifier ); - void commandEnd(); + void commandStart( const QString &identifier, int lineNumber); + void commandEnd(int lineNumber); void testStart( const QString &identifier ); void testEnd(); void testListStart(); void testListEnd(); - void blockStart(); - void blockEnd(); + void blockStart(int lineNumber); + void blockEnd(int lineNumber); void stringListArgumentStart(); void stringListArgumentEnd(); void stringListEntry( const QString &string, bool multiline, const QString &hashComment ); void hashComment( const QString &comment ); void bracketComment( const QString &comment ); void lineFeed(); void error( const KSieve::Error &error ); void finished(); QString result() const; QString error() const; bool hasError() const; void clear(); QDomDocument toDom() const; private: void write( const QString &msg ); void write( const QString &key, const QString &value ); void write( const QString &key, const QString &attribute, const QString & value ); QString mResult; QString mError; bool mIsAction; }; } #endif diff --git a/libksieve/ksieveui/util/util.cpp b/libksieve/ksieveui/util/util.cpp index 30c620f5b1..656ab37f0c 100644 --- a/libksieve/ksieveui/util/util.cpp +++ b/libksieve/ksieveui/util/util.cpp @@ -1,168 +1,210 @@ /******************************************************************************* ** ** Filename : util ** Created on : 03 April, 2005 ** Copyright : (c) 2005 Till Adam ** Email : ** *******************************************************************************/ /******************************************************************************* ** ** 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. ** ** It 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 ** ** In addition, as a special exception, the copyright holders give ** permission to link the code of this program with any edition of ** the Qt library by Trolltech AS, Norway (or with modified versions ** of Qt that use the same license as Qt), and distribute linked ** combinations including the two. You must obey the GNU General ** Public License in all respects for all of the code used other than ** Qt. If you modify this file, you may extend this exception to ** your version of the file, but you are not obligated to do so. If ** you do not wish to do so, delete this exception statement from ** your version. ** *******************************************************************************/ #include "util.h" #include "pimcommon/util/pimutil.h" #include "imapresourcesettings.h" #include "sieve-vacation.h" #include #include #include #include using namespace KSieveUi; KUrl KSieveUi::Util::findSieveUrlForAccount( const QString &identifier ) { QScopedPointer interface( PimCommon::Util::createImapSettingsInterface(identifier) ); if ( !interface->sieveSupport() ) return KUrl(); if ( interface->sieveReuseConfig() ) { // assemble Sieve url from the settings of the account: KUrl u; u.setScheme( QLatin1String("sieve") ); QString server; QDBusReply reply = interface->imapServer(); if ( reply.isValid() ) { server = reply; server = server.section( QLatin1Char(':'), 0, 0 ); } else { return KUrl(); } u.setHost( server ); u.setUserName( interface->userName() ); QDBusInterface resourceSettings( QLatin1String( "org.freedesktop.Akonadi.Resource." ) + identifier, QLatin1String("/Settings"), QLatin1String("org.kde.Akonadi.Imap.Wallet") ); QString pwd; QDBusReply replyPass = resourceSettings.call( QLatin1String("password") ); if ( replyPass.isValid() ) { pwd = replyPass; } u.setPassword( pwd ); u.setPort( interface->sievePort() ); QString authStr; switch( interface->authentication() ) { case MailTransport::Transport::EnumAuthenticationType::CLEAR: case MailTransport::Transport::EnumAuthenticationType::PLAIN: authStr = QLatin1String("PLAIN"); break; case MailTransport::Transport::EnumAuthenticationType::LOGIN: authStr = QLatin1String("LOGIN"); break; case MailTransport::Transport::EnumAuthenticationType::CRAM_MD5: authStr = QLatin1String("CRAM-MD5"); break; case MailTransport::Transport::EnumAuthenticationType::DIGEST_MD5: authStr = QLatin1String("DIGEST-MD5"); break; case MailTransport::Transport::EnumAuthenticationType::GSSAPI: authStr = QLatin1String("GSSAPI"); break; case MailTransport::Transport::EnumAuthenticationType::ANONYMOUS: authStr = QLatin1String("ANONYMOUS"); break; default: authStr = QLatin1String("PLAIN"); break; } u.addQueryItem( QLatin1String("x-mech"), authStr ); const QString resultSafety = interface->safety(); if ( resultSafety == QLatin1String("None")) u.addQueryItem( QLatin1String("x-allow-unencrypted"), QLatin1String("true") ); u.setFileName( interface->sieveVacationFilename() ); return u; } else { KUrl u( interface->sieveAlternateUrl() ); const QString resultSafety = interface->safety(); if ( u.protocol().toLower() == QLatin1String("sieve") && ( resultSafety == QLatin1String("None") ) && u.queryItem(QLatin1String("x-allow-unencrypted")).isEmpty() ) u.addQueryItem( QLatin1String("x-allow-unencrypted"), QLatin1String("true") ); const QString resultCustomAuthentication = interface->sieveCustomAuthentification(); if (resultCustomAuthentication == QLatin1String("ImapUserPassword")) { u.setUserName( interface->userName() ); QDBusInterface resourceSettings( QLatin1String( "org.freedesktop.Akonadi.Resource." ) + identifier, QLatin1String("/Settings"), QLatin1String("org.kde.Akonadi.Imap.Wallet") ); QString pwd; QDBusReply replyPass = resourceSettings.call( QLatin1String("password") ); if ( replyPass.isValid() ) { pwd = replyPass; } u.setPassword( pwd ); } else if (resultCustomAuthentication == QLatin1String("CustomUserPassword")) { QDBusInterface resourceSettings( QLatin1String( "org.freedesktop.Akonadi.Resource." ) + identifier, QLatin1String("/Settings"), QLatin1String("org.kde.Akonadi.Imap.Wallet") ); QString pwd; QDBusReply replyPass = resourceSettings.call( QLatin1String("sieveCustomPassword") ); if ( replyPass.isValid() ) { pwd = replyPass; } u.setPassword( pwd ); u.setUserName( interface->sieveCustomUsername() ); } u.setFileName( interface->sieveVacationFilename() ); return u; } } Akonadi::AgentInstance::List KSieveUi::Util::imapAgentInstances() { Akonadi::AgentInstance::List relevantInstances; foreach ( const Akonadi::AgentInstance &instance, Akonadi::AgentManager::self()->instances() ) { if ( instance.type().mimeTypes().contains( KMime::Message::mimeType() ) && instance.type().capabilities().contains( QLatin1String("Resource") ) && !instance.type().capabilities().contains( QLatin1String("Virtual") ) ) { if ( instance.identifier().contains( IMAP_RESOURCE_IDENTIFIER ) || instance.identifier().contains( KOLAB_RESOURCE_IDENTIFIER ) ) relevantInstances << instance; } } return relevantInstances; } bool KSieveUi::Util::checkOutOfOfficeOnStartup() { return VacationSettings::self()->checkOutOfOfficeOnStartup(); } bool KSieveUi::Util::allowOutOfOfficeSettings() { return VacationSettings::self()->allowOutOfOfficeSettings(); } + +bool Util::hasKep14Support(const QStringList &sieveCapabilities, const QStringList &availableScripts, const QString &activeScript) +{ + const bool hasIncludeCapability = sieveCapabilities.contains(QLatin1String("include")); + if (!hasIncludeCapability) { + return false; + } + + bool masterIsActive = !activeScript.isEmpty(); + if (masterIsActive) { + const QString scriptName = activeScript.split(QLatin1Char('.')).first().toLower(); + masterIsActive = (scriptName == QLatin1String("master") || scriptName == QLatin1String("user")); + } + if (!masterIsActive) { + return false; + } + + bool hasUserScript = false; + foreach(const QString &script, availableScripts) { + if (script.isEmpty()) { + continue; + } + const QString name = script.split(QLatin1Char('.')).first().toLower(); + if (name == QLatin1String("user")) { + hasUserScript = true; + break; + } + } + + return hasIncludeCapability && masterIsActive && hasUserScript; +} + +bool Util::isKep14ProtectedName(const QString &name) +{ + QString n = name.split(QLatin1Char('.')).first().toLower(); + if (n == QLatin1String("master") || + n == QLatin1String("user") || + n == QLatin1String("management")) { + return true; + } + return false; +} \ No newline at end of file diff --git a/libksieve/ksieveui/util/util.h b/libksieve/ksieveui/util/util.h index a1a83cc61d..04621177e0 100644 --- a/libksieve/ksieveui/util/util.h +++ b/libksieve/ksieveui/util/util.h @@ -1,82 +1,94 @@ /******************************************************************************* ** ** Filename : util ** Created on : 03 April, 2005 ** Copyright : (c) 2005 Till Adam ** Email : ** *******************************************************************************/ /******************************************************************************* ** ** 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. ** ** It 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 ** ** In addition, as a special exception, the copyright holders give ** permission to link the code of this program with any edition of ** the Qt library by Trolltech AS, Norway (or with modified versions ** of Qt that use the same license as Qt), and distribute linked ** combinations including the two. You must obey the GNU General ** Public License in all respects for all of the code used other than ** Qt. If you modify this file, you may extend this exception to ** your version of the file, but you are not obligated to do so. If ** you do not wish to do so, delete this exception statement from ** your version. ** *******************************************************************************/ #ifndef KSIEVE_KSIEVEUI_UTIL_H #define KSIEVE_KSIEVEUI_UTIL_H #include "ksieveui_export.h" #include class KUrl; class QString; +class QStringList; namespace KSieveUi { /** * The Util namespace contains a collection of helper functions use in * various places. */ namespace Util { /** * Returns the sieve url for the account with the given @p identifier. */ KSIEVEUI_EXPORT KUrl findSieveUrlForAccount( const QString &identifier ); /** * Returns the list of configured IMAP agent instances. */ KSIEVEUI_EXPORT Akonadi::AgentInstance::List imapAgentInstances(); /** * Returns whether the availability of a vacation sieve script shall * be checked at the start up of an application. */ KSIEVEUI_EXPORT bool checkOutOfOfficeOnStartup(); /** * Returns whether the functionality of vacation sieve script editing shall * be available at all. */ KSIEVEUI_EXPORT bool allowOutOfOfficeSettings(); + + /** + * Checks if a server has KEP:14 support + */ + bool hasKep14Support(const QStringList &sieveCapabilities, const QStringList &availableScripts, const QString &activeScript); + + /** + * Is the given scriptName a protected KEP:14 name, that a normal user should not touch directly. + * it tests against MASTER, USER and MANAGEMENT script + */ + bool isKep14ProtectedName(const QString &scriptName); } } #endif diff --git a/libksieve/ksieveui/vacation/multiimapvacationdialog.cpp b/libksieve/ksieveui/vacation/multiimapvacationdialog.cpp index 7afe53ec88..3619759813 100644 --- a/libksieve/ksieveui/vacation/multiimapvacationdialog.cpp +++ b/libksieve/ksieveui/vacation/multiimapvacationdialog.cpp @@ -1,158 +1,156 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #include "multiimapvacationdialog.h" #include "vacationpagewidget.h" +#include "multiimapvacationmanager.h" #include "ksieveui/util/util.h" #include #include #include #include #include #include #include #include #include using namespace KSieveUi; -MultiImapVacationDialog::MultiImapVacationDialog(QWidget *parent) +MultiImapVacationDialog::MultiImapVacationDialog(MultiImapVacationManager *manager, QWidget *parent) : KDialog(parent) + , mVacationManager(manager) { setCaption( i18n("Configure \"Out of Office\" Replies") ); KWindowSystem::setIcons( winId(), qApp->windowIcon().pixmap(IconSize(KIconLoader::Desktop),IconSize(KIconLoader::Desktop)), qApp->windowIcon().pixmap(IconSize(KIconLoader::Small),IconSize(KIconLoader::Small)) ); mStackedWidget = new QStackedWidget; setMainWidget(mStackedWidget); mTabWidget = new KTabWidget; mStackedWidget->addWidget(mTabWidget); QWidget *w = new QWidget; QVBoxLayout *vbox = new QVBoxLayout; w->setLayout(vbox); QLabel *lab = new QLabel(i18n("KMail's Out of Office Reply functionality relies on " "server-side filtering. You have not yet configured an " "IMAP server for this. " "You can do this on the \"Filtering\" tab of the IMAP " "account configuration.")); lab->setWordWrap(true); vbox->addWidget(lab); vbox->addStretch(); mStackedWidget->addWidget(w); mStackedWidget->setCurrentIndex(0); init(); readConfig(); connect(this, SIGNAL(okClicked()), this, SLOT(slotOkClicked())); connect(this, SIGNAL(defaultClicked()), this, SLOT(slotDefaultClicked())); } MultiImapVacationDialog::~MultiImapVacationDialog() { writeConfig(); } void MultiImapVacationDialog::switchToServerNamePage(const QString &serverName) { for (int i=0; i < mTabWidget->count(); ++i) { if (mTabWidget->tabText(i) == serverName) { mTabWidget->setCurrentIndex(i); break; } } } QList MultiImapVacationDialog::listCreateJob() const { return mListCreateJob; } void MultiImapVacationDialog::init() { bool foundOneImap = false; - const Akonadi::AgentInstance::List instances = KSieveUi::Util::imapAgentInstances(); - foreach ( const Akonadi::AgentInstance &instance, instances ) { - if ( instance.status() == Akonadi::AgentInstance::Broken ) - continue; - - const KUrl url = KSieveUi::Util::findSieveUrlForAccount( instance.identifier() ); - if ( !url.isEmpty() ) { - const QString serverName = instance.name(); - createPage(serverName, url); - foundOneImap = true; - } + + QMap list = mVacationManager->serverList(); + foreach (const QString &serverName, list.keys()) { + const KUrl url = list.value(serverName); + createPage(serverName, url); + foundOneImap = true; } if (foundOneImap) { setButtons( Ok | Cancel | Default ); setDefaultButton( Ok ); } else { mStackedWidget->setCurrentIndex(1); setButtons( Close ); } if (mTabWidget->count() <= 1) mTabWidget->setTabBarHidden(true); } void MultiImapVacationDialog::createPage(const QString &serverName, const KUrl &url) { VacationPageWidget *page = new VacationPageWidget; page->setServerUrl(url); page->setServerName(serverName); + page->setVacationManager(mVacationManager); mTabWidget->addTab(page,serverName); } void MultiImapVacationDialog::readConfig() { KConfigGroup group( KGlobal::config(), "MultiImapVacationDialog" ); const QSize size = group.readEntry( "Size", QSize() ); if ( size.isValid() ) { resize( size ); } else { resize( sizeHint().width(), sizeHint().height() ); } } void MultiImapVacationDialog::writeConfig() { KConfigGroup group( KGlobal::config(), "MultiImapVacationDialog" ); group.writeEntry( "Size", size() ); } void MultiImapVacationDialog::slotOkClicked() { for (int i=0; i < mTabWidget->count(); ++i) { VacationPageWidget *vacationPage = qobject_cast(mTabWidget->widget(i)); if (vacationPage) { VacationCreateScriptJob *job = vacationPage->writeScript(); if (job) mListCreateJob.append(job); } } } void MultiImapVacationDialog::slotDefaultClicked() { for (int i=0; i < mTabWidget->count(); ++i) { VacationPageWidget *vacationPage = qobject_cast(mTabWidget->widget(i)); if (vacationPage) { vacationPage->setDefault(); } } } diff --git a/libksieve/ksieveui/vacation/multiimapvacationdialog.h b/libksieve/ksieveui/vacation/multiimapvacationdialog.h index b6035882c7..248837b844 100644 --- a/libksieve/ksieveui/vacation/multiimapvacationdialog.h +++ b/libksieve/ksieveui/vacation/multiimapvacationdialog.h @@ -1,55 +1,57 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #ifndef MULTIIMAPVACATIONDIALOG_H #define MULTIIMAPVACATIONDIALOG_H #include #include "ksieveui_export.h" class KTabWidget; class QStackedWidget; namespace KSieveUi { class VacationCreateScriptJob; +class MultiImapVacationManager; class KSIEVEUI_EXPORT MultiImapVacationDialog : public KDialog { Q_OBJECT public: - explicit MultiImapVacationDialog(QWidget *parent=0); + explicit MultiImapVacationDialog(MultiImapVacationManager *manager, QWidget *parent=0); ~MultiImapVacationDialog(); QList listCreateJob() const; void switchToServerNamePage(const QString &serverName); private slots: void slotOkClicked(); void slotDefaultClicked(); private: void createPage(const QString &serverName, const KUrl &url); void init(); void readConfig(); void writeConfig(); QList mListCreateJob; KTabWidget *mTabWidget; QStackedWidget *mStackedWidget; + MultiImapVacationManager* mVacationManager; }; } #endif // MULTIIMAPVACATIONDIALOG_H diff --git a/libksieve/ksieveui/vacation/multiimapvacationmanager.cpp b/libksieve/ksieveui/vacation/multiimapvacationmanager.cpp index 561ca28bc1..ed48d2b24f 100644 --- a/libksieve/ksieveui/vacation/multiimapvacationmanager.cpp +++ b/libksieve/ksieveui/vacation/multiimapvacationmanager.cpp @@ -1,84 +1,135 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #include "multiimapvacationmanager.h" #include "vacationcheckjob.h" #include "util/util.h" +#include +#include +#include #include #include #include +#include + using namespace KSieveUi; MultiImapVacationManager::MultiImapVacationManager(QObject *parent) : QObject(parent), mNumberOfJobs(0), - mQuestionAsked(false), mCheckInProgress(false) { } MultiImapVacationManager::~MultiImapVacationManager() { } -void MultiImapVacationManager::checkVacation() +QMap MultiImapVacationManager::serverList() { - if (mCheckInProgress) - return; - mNumberOfJobs = 0; - mCheckInProgress = true; - mQuestionAsked = false; - + QMap list; const Akonadi::AgentInstance::List instances = KSieveUi::Util::imapAgentInstances(); foreach ( const Akonadi::AgentInstance &instance, instances ) { if ( instance.status() == Akonadi::AgentInstance::Broken ) continue; const KUrl url = KSieveUi::Util::findSieveUrlForAccount( instance.identifier() ); if ( !url.isEmpty() ) { - const QString serverName = instance.name(); - ++mNumberOfJobs; - VacationCheckJob *job = new VacationCheckJob(url, serverName, this); - connect(job, SIGNAL(scriptActive(bool,QString)), this, SLOT(slotScriptActive(bool,QString))); + list.insert(instance.name(), url); } } + return list; } -void MultiImapVacationManager::slotScriptActive(bool active, const QString &serverName) +void MultiImapVacationManager::checkVacation(const QString &serverName, const KUrl &url) { - --mNumberOfJobs; - Q_EMIT scriptActive(active, serverName); - - if (active) { - if (!mQuestionAsked) { - mQuestionAsked = true; - if ( KMessageBox::questionYesNo( 0, i18n( "There is still an active out-of-office reply configured.\n" - "Do you want to edit it?"), i18n("Out-of-office reply still active"), - KGuiItem( i18n( "Edit"), QLatin1String("document-properties") ), - KGuiItem( i18n("Ignore"), QLatin1String("dialog-cancel") ) ) - == KMessageBox::Yes ) { - Q_EMIT requestEditVacation(); - } - } + ++mNumberOfJobs; + if (!mKep14Support.contains(serverName)) { + CheckKep14SupportJob *checkKep14Job = new CheckKep14SupportJob(this); + checkKep14Job->setProperty(QLatin1String("triggerScript").latin1(), true); + checkKep14Job->setServerName(serverName); + checkKep14Job->setServerUrl(url); + connect(checkKep14Job, SIGNAL(result(CheckKep14SupportJob*,bool)), SLOT(slotCheckKep14Ended(CheckKep14SupportJob*,bool))); + checkKep14Job->start(); + } + + VacationCheckJob *job = new VacationCheckJob(url, serverName, this); + job->setKep14Support(mKep14Support[serverName]); + connect(job, SIGNAL(scriptActive(VacationCheckJob*,QString,bool)), this, SLOT(slotScriptActive(VacationCheckJob*,QString,bool))); + job->start(); +} + +void MultiImapVacationManager::checkVacation() +{ + if (mCheckInProgress) + return; + mNumberOfJobs = 0; + mCheckInProgress = true; + + QMap list = serverList(); + foreach ( const QString &serverName, list.keys() ) { + const KUrl url = list.value(serverName); + checkVacation(serverName, url); } +} - if (mNumberOfJobs == 0) +void MultiImapVacationManager::slotScriptActive(VacationCheckJob* job, QString scriptName, bool active) +{ + --mNumberOfJobs; + if (mNumberOfJobs == 0) { mCheckInProgress = false; + } + + job->deleteLater(); + + if (job->noScriptFound()) { + emit scriptActive(false, job->serverName()); + return; + } + emit scriptActive(active, job->serverName()); + emit scriptAvailable(job->serverName(), job->sieveCapabilities(), scriptName, job->script(), active); +} + +void MultiImapVacationManager::slotCheckKep14Ended(CheckKep14SupportJob *job, bool success) +{ + job->deleteLater(); + if (!success) { + --mNumberOfJobs; + return; + } + + mKep14Support.insert(job->serverName(), job->hasKep14Support()); + + VacationCheckJob *checkJob = new VacationCheckJob(job->serverUrl(), job->serverName(), this); + checkJob->setKep14Support(job->hasKep14Support()); + connect(checkJob, SIGNAL(scriptActive(VacationCheckJob*,QString,bool)), + SLOT(slotScriptActive(VacationCheckJob*,QString,bool))); + checkJob->start(); +} + +bool MultiImapVacationManager::kep14Support(QString serverName) +{ + if (mKep14Support.contains(serverName)) { + return mKep14Support[serverName]; + } else { + kWarning() << "We don't know the KEP:14 support for this server." << serverName; + } + return false; } diff --git a/libksieve/ksieveui/vacation/multiimapvacationmanager.h b/libksieve/ksieveui/vacation/multiimapvacationmanager.h index c61fee4e9d..ca46b38b3a 100644 --- a/libksieve/ksieveui/vacation/multiimapvacationmanager.h +++ b/libksieve/ksieveui/vacation/multiimapvacationmanager.h @@ -1,48 +1,59 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #ifndef MULTIIMAPVACATIONMANAGER_H #define MULTIIMAPVACATIONMANAGER_H #include +#include #include "ksieveui_export.h" +class KUrl; + namespace KSieveUi { +class CheckKep14SupportJob; +class VacationCheckJob; class KSIEVEUI_EXPORT MultiImapVacationManager : public QObject { Q_OBJECT public: explicit MultiImapVacationManager(QObject *parent=0); ~MultiImapVacationManager(); void checkVacation(); + QMap serverList(); + void checkVacation(const QString &serverName, const KUrl &url); + + bool kep14Support(QString serverName); Q_SIGNALS: void scriptActive(bool active, const QString &serverName); - void requestEditVacation(); + void scriptAvailable(const QString &serverName, const QStringList &sieveCapabilities, const QString &scriptName, const QString &script, bool active); private slots: - void slotScriptActive(bool active, const QString &serverName); + void slotScriptActive(VacationCheckJob* job, QString scriptName, bool active); + void slotCheckKep14Ended(CheckKep14SupportJob *job, bool success); private: int mNumberOfJobs; - bool mQuestionAsked; bool mCheckInProgress; + + QMap mKep14Support; //if the server has KEP:14 support }; } #endif // MULTIIMAPVACATIONMANAGER_H diff --git a/libksieve/ksieveui/vacation/tests/CMakeLists.txt b/libksieve/ksieveui/vacation/tests/CMakeLists.txt index 546a63f1d8..200e679e2f 100644 --- a/libksieve/ksieveui/vacation/tests/CMakeLists.txt +++ b/libksieve/ksieveui/vacation/tests/CMakeLists.txt @@ -1,12 +1,36 @@ include_directories(${CMAKE_SOURCE_DIR}/libksieve ${CMAKE_SOURCE_DIR}/libksieve/ksieveui + ${CMAKE_BINARY_DIR}/libksieve + ${CMAKE_BINARY_DIR}/libksieve/ksieveui ) set(vacation_multi_server_SRCS main.cpp ) KDE4_ADD_EXECUTABLE(vacationmultiservertest ${vacation_multi_server_SRCS} ) TARGET_LINK_LIBRARIES(vacationmultiservertest ${KDE4_KDEUI_LIBS} ksieveui ksieve) +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) + +macro( add_vacation_test _source ) + set( _test ${_source}test.cpp + ../${_source}.cpp + ../vacationscriptextractor.cpp + ${CMAKE_BINARY_DIR}/libksieve/ksieveui/sieve-vacation.cpp + ${CMAKE_SOURCE_DIR}/libksieve/parser/lexer.cpp + ${CMAKE_SOURCE_DIR}/libksieve/parser/parser.cpp + ${CMAKE_SOURCE_DIR}/libksieve/parser/utf8validator.cpp + ) + get_filename_component( _name ${_source} NAME_WE ) + kde4_add_unit_test( ${_name} TESTNAME vacation-${_name} ${_test} ) + set_target_properties(${_name} PROPERTIES COMPILE_FLAGS -DVACATIONTESTDATADIR="\\"${CMAKE_CURRENT_SOURCE_DIR}/data/\\"") + target_link_libraries( ${_name} + ksieveui + kmanagesieve + ksieve + ${QT_QTTEST_LIBRARY} ${QT_QTCORE_LIBRARY} ${KDE4_KDEUI_LIBS} ${KDEPIMLIBS_KMIME_LIBS} ${KDEPIMLIBS_KPIMIDENTITIES_LIBS}) +endmacro() + +add_vacation_test( vacationutils ) \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/tests/data/vacation-active-discard.siv b/libksieve/ksieveui/vacation/tests/data/vacation-active-discard.siv new file mode 100644 index 0000000000..8d7a1962b4 --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/data/vacation-active-discard.siv @@ -0,0 +1,9 @@ +require ["date","relational","vacation"]; +# EDITOR Roundcube (Managesieve) +# EDITOR_VERSION 8.2 +# rule:[Urlaub] +if allof (not header :contains "X-Spam-Flag" "YES") +{ + vacation :days 7 :addresses "test@test.de" :subject "XXX" "dsfgsdfgsdfg"; + discard; +} \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/tests/data/vacation-complex-time.siv b/libksieve/ksieveui/vacation/tests/data/vacation-complex-time.siv new file mode 100644 index 0000000000..0b1c2def35 --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/data/vacation-complex-time.siv @@ -0,0 +1,8 @@ +require ["date","relational","vacation"]; +# EDITOR Roundcube (Managesieve) +# EDITOR_VERSION 8.2 +# rule:[Urlaub] +if allof (currentdate :value "ge" "iso8601" "2015-01-02T02:00:00+00:00", currentdate :value "le" "date" "2015-03-04", not header :contains "X-Spam-Flag" "YES") +{ + vacation :days 7 :addresses "test@test.de" :subject "XXX" "dsfgsdfgsdfg"; +} \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/tests/data/vacation-complex.siv b/libksieve/ksieveui/vacation/tests/data/vacation-complex.siv new file mode 100644 index 0000000000..b7b4140bcf --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/data/vacation-complex.siv @@ -0,0 +1,8 @@ +require ["date","relational","vacation"]; +# EDITOR Roundcube (Managesieve) +# EDITOR_VERSION 8.2 +# rule:[Urlaub] +if allof (currentdate :zone "+0100" :value "ge" "date" "2015-01-02", currentdate :zone "+0100" :value "le" "date" "2015-03-04", not header :contains "X-Spam-Flag" "YES") +{ + vacation :days 7 :addresses "test@test.de" :subject "XXX" "dsfgsdfgsdfg"; +} \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/tests/data/vacation-deactivate-complex.siv b/libksieve/ksieveui/vacation/tests/data/vacation-deactivate-complex.siv new file mode 100644 index 0000000000..bbd94ab272 --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/data/vacation-deactivate-complex.siv @@ -0,0 +1,8 @@ +require ["date","relational","vacation"]; +# EDITOR Roundcube (Managesieve) +# EDITOR_VERSION 8.2 +# rule:[Urlaub] +if false # allof (currentdate :zone "+0100" :value "ge" "date" "2015-01-02", currentdate :zone "+0100" :value "le" "date" "2015-03-04", not header :contains "X-Spam-Flag" "YES") +{ + vacation :days 7 :addresses "test@test.de" :subject "XXX" "dsfgsdfgsdfg"; +} \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/tests/data/vacation-deactivate-multiple.siv b/libksieve/ksieveui/vacation/tests/data/vacation-deactivate-multiple.siv new file mode 100644 index 0000000000..1889dc2269 --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/data/vacation-deactivate-multiple.siv @@ -0,0 +1,19 @@ +require ["vacation"]; + +if true +{ + testcommand; +} + +# EDITOR Roundcube (Managesieve) +# EDITOR_VERSION 8.2 +# rule:[Urlaub] +if false # true +{ + vacation :subject "XXX" "dsfgsdfgsdfg"; +} + +if true +{ + testcommand; +} \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/tests/data/vacation-deactivate.siv b/libksieve/ksieveui/vacation/tests/data/vacation-deactivate.siv new file mode 100644 index 0000000000..c2b15a22db --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/data/vacation-deactivate.siv @@ -0,0 +1,9 @@ +require ["vacation"]; + +# EDITOR Roundcube (Managesieve) +# EDITOR_VERSION 8.2 +# rule:[Urlaub] +if false # true +{ + vacation :subject "XXX" "dsfgsdfgsdfg"; +} \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/tests/data/vacation-deactive-copy.siv b/libksieve/ksieveui/vacation/tests/data/vacation-deactive-copy.siv new file mode 100644 index 0000000000..def848a2cf --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/data/vacation-deactive-copy.siv @@ -0,0 +1,10 @@ +require ["vacation", "copy"]; +# EDITOR Roundcube (Managesieve) +# EDITOR_VERSION 8.2 +# rule:[Urlaub] +if false +{ + vacation :days 7 :addresses "test@test.de" :subject "XXX" "dsfgsdfgsdfg"; + redirect :copy "copy@example.org"; +} +discard; \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/tests/data/vacation-deactive-send.siv b/libksieve/ksieveui/vacation/tests/data/vacation-deactive-send.siv new file mode 100644 index 0000000000..7be2f4d094 --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/data/vacation-deactive-send.siv @@ -0,0 +1,11 @@ +require ["vacation", "copy"]; +# EDITOR Roundcube (Managesieve) +# EDITOR_VERSION 8.2 +# rule:[Urlaub] +if false +{ + vacation :days 7 :addresses "test@test.de" :subject "XXX" "dsfgsdfgsdfg"; + redirect "redirect@example.org"; +} + +redirect :copy "argh@example.org"; \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/tests/data/vacation-multiple.siv b/libksieve/ksieveui/vacation/tests/data/vacation-multiple.siv new file mode 100644 index 0000000000..7737faf738 --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/data/vacation-multiple.siv @@ -0,0 +1,16 @@ +require ["vacation"]; + +if false +{ + testcommand; +} + +# EDITOR Roundcube (Managesieve) +# EDITOR_VERSION 8.2 +# rule:[Urlaub] +vacation :subject "XXX" "dsfgsdfgsdfg"; + +if false +{ + testcommand; +} diff --git a/libksieve/ksieveui/vacation/tests/data/vacation-notfound.siv b/libksieve/ksieveui/vacation/tests/data/vacation-notfound.siv new file mode 100644 index 0000000000..ce93aeefb2 --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/data/vacation-notfound.siv @@ -0,0 +1,15 @@ +#blabla + +testcommand; + +if true { + testcmd2; +} + +if false { + testcmd3; +} + +if true { + testcmd4; +} \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/tests/data/vacation-simple.siv b/libksieve/ksieveui/vacation/tests/data/vacation-simple.siv new file mode 100644 index 0000000000..ab6c321800 --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/data/vacation-simple.siv @@ -0,0 +1,6 @@ +require ["vacation"]; + +# EDITOR Roundcube (Managesieve) +# EDITOR_VERSION 8.2 +# rule:[Urlaub] +vacation :subject "XXX" "dsfgsdfgsdfg"; \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/tests/main.cpp b/libksieve/ksieveui/vacation/tests/main.cpp index 773a887ec3..2b8a6dcc48 100644 --- a/libksieve/ksieveui/vacation/tests/main.cpp +++ b/libksieve/ksieveui/vacation/tests/main.cpp @@ -1,38 +1,40 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #include #include #include #include "vacation/multiimapvacationdialog.h" +#include #include int main( int argc, char** argv ) { KCmdLineArgs::init(argc, argv, "vacationmultiscripttest", 0, ki18n("VacationMultiScriptTest_Gui"), "1.0", ki18n("Test for dialog when server has multiscript")); KApplication app; - app.setQuitOnLastWindowClosed( false ); + app.setQuitOnLastWindowClosed( true ); - KSieveUi::MultiImapVacationDialog dlg; + KSieveUi::MultiImapVacationManager manager; + KSieveUi::MultiImapVacationDialog dlg(&manager); dlg.show(); app.exec(); return 0; } diff --git a/libksieve/ksieveui/vacation/tests/vacationutilstest.cpp b/libksieve/ksieveui/vacation/tests/vacationutilstest.cpp new file mode 100644 index 0000000000..758796bf5e --- /dev/null +++ b/libksieve/ksieveui/vacation/tests/vacationutilstest.cpp @@ -0,0 +1,342 @@ +/* + Copyright (c) 2015 Sandro KnauĂź + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + 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 +*/ +#include "vacationutilstest.h" +#include "vacation/vacationutils.h" + +#include + +#include +#include +#include + +using namespace KSieveUi; + +QTEST_KDEMAIN( VacationUtilsTest, NoGUI ) + +void testAliases(KMime::Types::AddrSpecList l1, KMime::Types::AddrSpecList l2) +{ + QCOMPARE(l1.count(),l2.count()); + for (int i=0; i < l1.count(); i++) { + QCOMPARE(l1.at(i).asString(),l2.at(i).asString()); + } +} + +void testAliases(KMime::Types::AddrSpecList l1, QStringList l2) +{ + QCOMPARE(l1.count(),l2.count()); + for (int i=0;i < l1.count(); i++) { + QCOMPARE(l1.at(i).asString(),l2.at(i)); + } +} + + +void VacationUtilsTest::testParseEmptyScript() +{ + const QString script; + QCOMPARE(VacationUtils::parseScript(script).isValid(), false); +} + +void VacationUtilsTest::testParseOnlyComment() +{ + QString script(QLatin1String("#comment")); + QCOMPARE(VacationUtils::parseScript(script).isValid(), false); + script = QLatin1String("#comment\n\n#comment\n"); + QCOMPARE(VacationUtils::parseScript(script).isValid(), false); +} + +void VacationUtilsTest::testParseActivate_data() +{ + QTest::addColumn("filename"); + QTest::addColumn("found"); + QTest::addColumn("active"); + + QTest::newRow("notfound") << QString::fromLatin1("vacation-notfound.siv") << false << false; + QTest::newRow("simple") << QString::fromLatin1("vacation-simple.siv") << true << true; + QTest::newRow("multile if") << QString::fromLatin1("vacation-multiple.siv") << true << true; + QTest::newRow("deactivate") << QString::fromLatin1("vacation-deactivate.siv") << true << false; + QTest::newRow("deactivate-multiple if") << QString::fromLatin1("vacation-deactivate-multiple.siv") << true << false; + QTest::newRow("deactivate-complex") << QString::fromLatin1("vacation-deactivate-complex.siv") << true << false; +} + + +void VacationUtilsTest::testParseActivate() +{ + QFETCH(QString, filename); + QFETCH(bool, found); + QFETCH(bool, active); + + QFile file(QLatin1String(VACATIONTESTDATADIR)+filename); + QVERIFY(file.open(QIODevice::ReadOnly)); + QString script = QString::fromUtf8(file.readAll()); + + VacationUtils::Vacation vacation = VacationUtils::parseScript(script); + QCOMPARE(vacation.isValid(), found); + QCOMPARE(vacation.active, active); +} + +void VacationUtilsTest::testParseScript_data() +{ + QTest::addColumn("activate"); + QTest::addColumn("deactivate"); + + QTest::newRow("simple") << QString::fromLatin1("vacation-simple.siv") << QString::fromLatin1("vacation-deactivate.siv"); + QTest::newRow("complex") << QString::fromLatin1("vacation-complex.siv") << QString::fromLatin1("vacation-deactivate-complex.siv"); +} + + +void VacationUtilsTest::testParseScript() +{ + QFETCH(QString, activate); + QFETCH(QString, deactivate); + QFile fileA(QLatin1String(VACATIONTESTDATADIR) + activate); + QVERIFY(fileA.open(QIODevice::ReadOnly)); + QString scriptA = QString::fromUtf8(fileA.readAll()); + QFile fileD(QLatin1String(VACATIONTESTDATADIR) + deactivate); + QVERIFY(fileD.open(QIODevice::ReadOnly)); + QString scriptD = QString::fromUtf8(fileD.readAll()); + + VacationUtils::Vacation vacationA = VacationUtils::parseScript(scriptA); + VacationUtils::Vacation vacationD = VacationUtils::parseScript(scriptD); + QCOMPARE(vacationA.active, true); + QCOMPARE(vacationD.active, false); + QCOMPARE(vacationD.messageText, vacationA.messageText); + QCOMPARE(vacationD.subject, vacationA.subject); + QCOMPARE(vacationD.notificationInterval, vacationA.notificationInterval); + testAliases(vacationD.aliases, vacationA.aliases); + QCOMPARE(vacationD.sendForSpam, vacationA.sendForSpam); + QCOMPARE(vacationD.excludeDomain, vacationA.excludeDomain); + QCOMPARE(vacationD.startDate, vacationA.startDate); + QCOMPARE(vacationD.endDate, vacationA.endDate); + QCOMPARE(vacationD.startTime, QTime()); + QCOMPARE(vacationD.endTime, QTime()); +} + +void VacationUtilsTest::testMailAction_data() +{ + QTest::addColumn("filename"); + QTest::addColumn("action"); + QTest::addColumn("recipient"); + + QTest::newRow("keep") << QString::fromLatin1("vacation-complex.siv") << (int)VacationUtils::Keep << QString(); + QTest::newRow("discard") << QString::fromLatin1("vacation-active-discard.siv") << (int)VacationUtils::Discard << QString(); + QTest::newRow("send") << QString::fromLatin1("vacation-deactive-send.siv") << (int)VacationUtils::Sendto << QString::fromLatin1("redirect@example.org"); + QTest::newRow("copy") << QString::fromLatin1("vacation-deactive-copy.siv") << (int)VacationUtils::CopyTo << QString::fromLatin1("copy@example.org"); +} + +void VacationUtilsTest::testMailAction() +{ + QFETCH(QString, filename); + QFETCH(int, action); + QFETCH(QString, recipient); + + QFile file(QLatin1String(VACATIONTESTDATADIR) + filename); + QVERIFY(file.open(QIODevice::ReadOnly)); + QString script = QString::fromUtf8(file.readAll()); + + VacationUtils::Vacation vacation = VacationUtils::parseScript(script); + QCOMPARE((int)vacation.mailAction, action); + QCOMPARE(vacation.mailActionRecipient, recipient); + + const QString composedScript = VacationUtils::composeScript(vacation); + vacation = VacationUtils::parseScript(composedScript); + QCOMPARE((int)vacation.mailAction, action); + QCOMPARE(vacation.mailActionRecipient, recipient); +} + +void VacationUtilsTest::testParseScriptComplex() +{ + QFile file(QLatin1String(VACATIONTESTDATADIR "vacation-complex.siv")); + QVERIFY(file.open(QIODevice::ReadOnly)); + QString script = QString::fromUtf8(file.readAll()); + + VacationUtils::Vacation vacation = VacationUtils::parseScript(script); + QCOMPARE(vacation.active, true); + QCOMPARE(vacation.messageText, QLatin1String("dsfgsdfgsdfg")); + QCOMPARE(vacation.subject, QLatin1String("XXX")); + QCOMPARE(vacation.notificationInterval, 7); + testAliases(vacation.aliases, QStringList() << QLatin1String("test@test.de")); + QCOMPARE(vacation.sendForSpam, false); + QCOMPARE(vacation.excludeDomain, QString()); + QCOMPARE(vacation.startDate, QDate(2015, 01, 02)); + QCOMPARE(vacation.endDate, QDate(2015, 03, 04)); + QCOMPARE(vacation.startTime, QTime()); + QCOMPARE(vacation.endTime, QTime()); +} + +void VacationUtilsTest::testParseScriptComplexTime() +{ + QFile file(QLatin1String(VACATIONTESTDATADIR "vacation-complex-time.siv")); + QVERIFY(file.open(QIODevice::ReadOnly)); + QString script = QString::fromUtf8(file.readAll()); + + VacationUtils::Vacation vacation = VacationUtils::parseScript(script); + QCOMPARE(vacation.active, true); + QCOMPARE(vacation.messageText, QLatin1String("dsfgsdfgsdfg")); + QCOMPARE(vacation.subject, QLatin1String("XXX")); + QCOMPARE(vacation.notificationInterval, 7); + testAliases(vacation.aliases, QStringList() << QLatin1String("test@test.de")); + QCOMPARE(vacation.sendForSpam, false); + QCOMPARE(vacation.excludeDomain, QString()); + QCOMPARE(vacation.startDate, QDate(2015, 01, 02)); + QCOMPARE(vacation.endDate, QDate(2015, 03, 04)); + QCOMPARE(vacation.startTime, QTime(2,0)); + QCOMPARE(vacation.endTime, QTime()); + + QString composedScript = VacationUtils::composeScript(vacation); + vacation = VacationUtils::parseScript(composedScript); + QCOMPARE(vacation.startTime, QTime(2,0)); + QCOMPARE(vacation.endTime, QTime()); +} + +void VacationUtilsTest::testWriteScript() +{ + VacationUtils::Vacation vacation, vacationA; + QStringList aliases = QStringList() << QLatin1String("test@test.de"); + vacation.valid = true; + + vacation.messageText = QLatin1String("dsfgsdfgsdfg"); + vacation.subject = QLatin1String("XXX"); + vacation.notificationInterval = 7; + vacation.sendForSpam = false; + vacation.excludeDomain = QLatin1String("example.org"); + vacation.startDate = QDate(2015, 01, 02); + vacation.endDate = QDate(2015, 03, 04); + vacation.active = true; + + foreach(const QString &alias, aliases) { + KMime::Types::Mailbox a; + a.fromUnicodeString(alias); + vacation.aliases.append(a.addrSpec()); + } + + QString script = VacationUtils::composeScript(vacation); + vacationA = VacationUtils::parseScript(script); + QCOMPARE(vacationA.isValid(), true); + QCOMPARE(vacationA.active, vacation.active); + QCOMPARE(vacationA.messageText, vacation.messageText); + QCOMPARE(vacationA.subject, vacation.subject); + QCOMPARE(vacationA.notificationInterval, vacation.notificationInterval); + testAliases(vacationA.aliases, vacation.aliases); + QCOMPARE(vacationA.sendForSpam, vacation.sendForSpam); + QCOMPARE(vacationA.excludeDomain, vacation.excludeDomain); + QCOMPARE(vacationA.startDate, vacation.startDate); + QCOMPARE(vacationA.endDate, vacation.endDate); + QCOMPARE(vacationA.startTime, QTime()); + QCOMPARE(vacationA.endTime, QTime()); + + vacation.active = false; + script = VacationUtils::composeScript(vacation); + vacationA = VacationUtils::parseScript(script); + QCOMPARE(vacationA.isValid(), true); + QCOMPARE(vacationA.active, vacation.active); + QCOMPARE(vacationA.messageText, vacation.messageText); + QCOMPARE(vacationA.subject, vacation.subject); + QCOMPARE(vacationA.notificationInterval, vacation.notificationInterval); + testAliases(vacationA.aliases, vacation.aliases); + QCOMPARE(vacationA.sendForSpam, vacation.sendForSpam); + QCOMPARE(vacationA.excludeDomain, vacation.excludeDomain); + QCOMPARE(vacationA.startDate, vacation.startDate); + QCOMPARE(vacationA.endDate, vacation.endDate); + QCOMPARE(vacationA.startTime, QTime()); + QCOMPARE(vacationA.endTime, QTime()); +} + + +void VacationUtilsTest::testWriteSimpleScript() +{ + VacationUtils::Vacation vacation; + vacation.valid = true; + vacation.messageText = QLatin1String("dsfgsdfgsdfg"); + vacation.subject = QLatin1String("XXX"); + vacation.notificationInterval = 7; + vacation.active = true; + vacation.sendForSpam = true; + + QString script = VacationUtils::composeScript(vacation); + VacationUtils::Vacation vacationA = VacationUtils::parseScript(script); + QCOMPARE(vacation.isValid(), true); + QCOMPARE(vacationA.active, vacation.active); + QCOMPARE(vacationA.messageText, vacation.messageText); + QCOMPARE(vacationA.subject, vacation.subject); + QCOMPARE(vacationA.notificationInterval, vacation.notificationInterval); + + vacation.active = false; + script = VacationUtils::composeScript(vacation); + vacationA = VacationUtils::parseScript(script); + QCOMPARE(vacation.isValid(), true); + QCOMPARE(vacationA.active, vacation.active); + QCOMPARE(vacationA.messageText, vacation.messageText); + QCOMPARE(vacationA.subject, vacation.subject); + QCOMPARE(vacationA.notificationInterval, vacation.notificationInterval); + +} + +void VacationUtilsTest::testUpdateVacationBlock() +{ + QFile fileA(QLatin1String(VACATIONTESTDATADIR "vacation-simple.siv")); + QVERIFY(fileA.open(QIODevice::ReadOnly)); + QString scriptA = QString::fromUtf8(fileA.readAll()); + + QFile fileB(QLatin1String(VACATIONTESTDATADIR "vacation-deactivate.siv")); + QVERIFY(fileB.open(QIODevice::ReadOnly)); + QString scriptB = QString::fromUtf8(fileB.readAll()); + + const QString attend = QLatin1String("if true\n{\ntestcmd;\n}\n"); + const QString require = QLatin1String("require [\"date\", \"test\"];"); + const QString scriptAattend = scriptA + QLatin1String("\n") + attend; + const QString scriptBattend = scriptB + QLatin1String("\n") + attend; + + QStringList linesA = scriptA.split(QLatin1Char('\n')); + QStringList header; + for(int i=0; i<5;i++ ){ + header.append(linesA.at(i)); + } + + QStringList vacation; + for(int i=5; i + Copyright (c) 2015 Sandro KnauĂź This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ -#ifndef VACATIONCHECKJOB_H -#define VACATIONCHECKJOB_H +#ifndef VACATIONUTILSTEST_H +#define VACATIONUTILSTEST_H #include -#include - -namespace KManageSieve { -class SieveJob; -} namespace KSieveUi { -class VacationCheckJob : public QObject +class VacationUtilsTest : public QObject { Q_OBJECT -public: - explicit VacationCheckJob(const KUrl &url, const QString &serverName, QObject *parent=0); - ~VacationCheckJob(); - -Q_SIGNALS: - void scriptActive(bool active, const QString &serverName); - -private slots: - void slotGetResult(KManageSieve::SieveJob *job, bool success, const QString &script, bool active); - -private: - QString mServerName; - KUrl mUrl; - KManageSieve::SieveJob * mSieveJob; +private Q_SLOTS: + void testParseEmptyScript(); + void testParseOnlyComment(); + void testParseActivate_data(); + void testParseActivate(); + void testParseScript_data(); + void testParseScript(); + void testParseScriptComplex(); + void testParseScriptComplexTime(); + void testMailAction_data(); + void testMailAction(); + void testWriteScript(); + void testWriteSimpleScript(); + void testUpdateVacationBlock(); + void testMergeRequireLine(); }; } - -#endif // VACATIONCHECKJOB_H +#endif // VACATIONUTILSTEST_H diff --git a/libksieve/ksieveui/vacation/vacation.cpp b/libksieve/ksieveui/vacation/vacation.cpp index 96689fa4bb..3ab6fe9b60 100644 --- a/libksieve/ksieveui/vacation/vacation.cpp +++ b/libksieve/ksieveui/vacation/vacation.cpp @@ -1,228 +1,235 @@ /* -*- c++ -*- vacation.cpp KMail, the KDE mail client. Copyright (c) 2002 Marc Mutz This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. 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, US */ #include "vacation.h" #include "vacationutils.h" #include "vacationscriptextractor.h" #include "sieve-vacation.h" #include "util/util.h" #include "vacationdialog.h" #include #include #include #include #include #include #include #include using namespace KSieveUi; Vacation::Vacation(QObject * parent, bool checkOnly, const KUrl &url) : QObject( parent ), mSieveJob( 0 ), mDialog( 0 ), mWasActive( false ), mCheckOnly( checkOnly ) { if (url.isEmpty()) { mUrl = findURL(mServerName); } else { mUrl = url; } kDebug() << "Vacation: found url \"" << mUrl.prettyUrl() <<"\""; if ( mUrl.isEmpty() ) // nothing to do... return; mSieveJob = KManageSieve::SieveJob::get( mUrl ); if (checkOnly) { mSieveJob->setInteractive( false ); } connect( mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), SLOT(slotGetResult(KManageSieve::SieveJob*,bool,QString,bool)) ); } Vacation::~Vacation() { if ( mSieveJob ) mSieveJob->kill(); mSieveJob = 0; delete mDialog; mDialog = 0; kDebug() << "~Vacation()"; } KUrl Vacation::findURL(QString &serverName) const { const Akonadi::AgentInstance::List instances = Util::imapAgentInstances(); foreach ( const Akonadi::AgentInstance &instance, instances ) { if ( instance.status() == Akonadi::AgentInstance::Broken ) continue; const KUrl url = Util::findSieveUrlForAccount( instance.identifier() ); if ( !url.isEmpty() ) { serverName = instance.name(); return url; } } return KUrl(); } void Vacation::slotGetResult( KManageSieve::SieveJob * job, bool success, const QString & script, bool active ) { kDebug() << success << ", ?," << active << ")" << endl << "script:" << endl << script; mSieveJob = 0; // job deletes itself after returning from this slot! if ( !mCheckOnly && mUrl.protocol() == QLatin1String("sieve") && !job->sieveCapabilities().contains(QLatin1String("vacation")) ) { KMessageBox::sorry( 0, i18n( "Your server did not list \"vacation\" in " "its list of supported Sieve extensions;\n" "without it, KMail cannot install out-of-" "office replies for you.\n" "Please contact your system administrator." ) ); emit result( false ); return; } const bool supportsDate = job->sieveCapabilities().contains(QLatin1String("date")); if ( !mDialog && !mCheckOnly ) mDialog = new VacationDialog( i18n("Configure \"Out of Office\" Replies"), 0, false ); - QString messageText = VacationUtils::defaultMessageText(); - QString subject = VacationUtils::defaultSubject(); - int notificationInterval = VacationUtils::defaultNotificationInterval(); - QStringList aliases = VacationUtils::defaultMailAliases(); - bool sendForSpam = VacationUtils::defaultSendForSpam(); - QString domainName = VacationUtils::defaultDomainName(); - QDate startDate = VacationUtils::defaultStartDate(); - QDate endDate = VacationUtils::defaultEndDate(); - if ( !success ) active = false; // default to inactive - - if ( !mCheckOnly && ( !success || !KSieveUi::VacationUtils::parseScript( script, messageText, subject, notificationInterval, aliases, sendForSpam, domainName, startDate, endDate ) ) ) + if ( !success ) { + active = false; // default to inactive + } + + KSieveUi::VacationUtils::Vacation vacation = KSieveUi::VacationUtils::parseScript(script); + + if ( !mCheckOnly && ( !success || (!vacation.isValid() && !script.trimmed().isEmpty())) ) { KMessageBox::information( 0, i18n("Someone (probably you) changed the " "vacation script on the server.\n" "KMail is no longer able to determine " "the parameters for the autoreplies.\n" "Default values will be used." ) ); - + } mWasActive = active; if ( mDialog ) { - mDialog->setActivateVacation( active ); - mDialog->setSubject(subject); - mDialog->setMessageText( messageText ); - mDialog->setNotificationInterval( notificationInterval ); - mDialog->setMailAliases( aliases.join(QLatin1String(", ")) ); - mDialog->setSendForSpam( sendForSpam ); - mDialog->setDomainName( domainName ); + mDialog->setActivateVacation( active && vacation.active ); + mDialog->setMailAction(vacation.mailAction, vacation.mailActionRecipient); + mDialog->setSubject(vacation.subject); + mDialog->setMessageText( vacation.messageText ); + mDialog->setNotificationInterval( vacation.notificationInterval ); + mDialog->setMailAliases( vacation.aliases ); + mDialog->setSendForSpam( vacation.sendForSpam ); + mDialog->setDomainName( vacation.excludeDomain ); mDialog->enableDomainAndSendForSpam( !VacationSettings::allowOutOfOfficeUploadButNoSettings() ); if (supportsDate) { mDialog->enableDates( supportsDate ); - mDialog->setStartDate( startDate ); - mDialog->setEndDate( endDate ); + mDialog->setStartDate( vacation.startDate ); + mDialog->setEndTime(vacation.endTime); + mDialog->setEndDate( vacation.endDate ); + mDialog->setEndTime(vacation.endTime); } connect( mDialog, SIGNAL(okClicked()), SLOT(slotDialogOk()) ); connect( mDialog, SIGNAL(cancelClicked()), SLOT(slotDialogCancel()) ); mDialog->show(); } emit scriptActive( mWasActive, mServerName ); if ( mCheckOnly && mWasActive ) { if ( KMessageBox::questionYesNo( 0, i18n( "There is still an active out-of-office reply configured.\n" "Do you want to edit it?"), i18n("Out-of-office reply still active"), KGuiItem( i18n( "Edit"), QLatin1String("document-properties") ), KGuiItem( i18n("Ignore"), QLatin1String("dialog-cancel") ) ) == KMessageBox::Yes ) { emit requestEditVacation(); } } } void Vacation::slotDialogOk() { kDebug(); // compose a new script: - const QString script = VacationUtils::composeScript( mDialog->messageText(), - mDialog->subject(), - mDialog->notificationInterval(), - mDialog->mailAliases(), - mDialog->sendForSpam(), - mDialog->domainName(), - mDialog->startDate(), - mDialog->endDate() ); const bool active = mDialog->activateVacation(); + VacationUtils::Vacation vacation; + vacation.valid = true; + vacation.active = active; + vacation.messageText = mDialog->messageText(); + vacation.subject = mDialog->subject(); + vacation.mailAction = mDialog->mailAction(); + vacation.mailActionRecipient = mDialog->mailActionRecipient(); + vacation.notificationInterval = mDialog->notificationInterval(); + vacation.aliases = mDialog->mailAliases(); + vacation.sendForSpam = mDialog->sendForSpam(); + vacation.excludeDomain = mDialog->domainName(); + vacation.startDate = mDialog->startDate(); + vacation.startTime = mDialog->startTime(); + vacation.endDate = mDialog->endDate(); + vacation.endTime = mDialog->endTime(); + const QString script = VacationUtils::composeScript(vacation); emit scriptActive( active, mServerName); kDebug() << "script:" << endl << script; // and commit the dialog's settings to the server: mSieveJob = KManageSieve::SieveJob::put( mUrl, script, active, mWasActive ); if ( active ) connect( mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), SLOT(slotPutActiveResult(KManageSieve::SieveJob*,bool)) ); else connect( mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), SLOT(slotPutInactiveResult(KManageSieve::SieveJob*,bool)) ); // destroy the dialog: mDialog->delayedDestruct(); mDialog = 0; } void Vacation::slotDialogCancel() { kDebug(); mDialog->delayedDestruct(); mDialog = 0; emit result( false ); } void Vacation::slotPutActiveResult( KManageSieve::SieveJob * job, bool success ) { handlePutResult( job, success, true ); } void Vacation::slotPutInactiveResult( KManageSieve::SieveJob * job, bool success ) { handlePutResult( job, success, false ); } void Vacation::handlePutResult( KManageSieve::SieveJob *, bool success, bool activated ) { if ( success ) KMessageBox::information( 0, activated ? i18n("Sieve script installed successfully on the server.\n" "Out of Office reply is now active.") : i18n("Sieve script installed successfully on the server.\n" "Out of Office reply has been deactivated.") ); kDebug() << "( ???," << success << ", ? )"; mSieveJob = 0; // job deletes itself after returning from this slot! emit result( success ); emit scriptActive( activated, mServerName ); } void Vacation::showVacationDialog() { if (mDialog) { mDialog->show(); mDialog->raise(); mDialog->activateWindow(); } } diff --git a/libksieve/ksieveui/vacation/vacationcheckjob.cpp b/libksieve/ksieveui/vacation/vacationcheckjob.cpp index 898aad9e89..f936be1166 100644 --- a/libksieve/ksieveui/vacation/vacationcheckjob.cpp +++ b/libksieve/ksieveui/vacation/vacationcheckjob.cpp @@ -1,47 +1,207 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #include "vacationcheckjob.h" +#include "vacationutils.h" +#include +#include #include +#include + using namespace KSieveUi; VacationCheckJob::VacationCheckJob(const KUrl &url, const QString &serverName, QObject *parent) : QObject(parent), mServerName(serverName), mUrl(url) + , mKep14Support(false) + , mSieveJob(0) + , mParseJob(0) + , mNoScriptFound(0) { - mSieveJob = KManageSieve::SieveJob::get( mUrl ); - mSieveJob->setInteractive( false ); - connect( mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), - SLOT(slotGetResult(KManageSieve::SieveJob*,bool,QString,bool)) ); } VacationCheckJob::~VacationCheckJob() +{ + kill(); +} + +void VacationCheckJob::kill() { if ( mSieveJob ) mSieveJob->kill(); mSieveJob = 0; + + if (mParseJob) { + mParseJob->kill(); + } + mParseJob = 0; } -void VacationCheckJob::slotGetResult(KManageSieve::SieveJob */*job*/, bool success, const QString &/*script*/, bool active) + +void VacationCheckJob::setKep14Support(bool kep14Support) { + mKep14Support = kep14Support; +} + +void VacationCheckJob::start() +{ + if (mKep14Support) { + KUrl url = mUrl; + url.setFileName(QLatin1String("USER")); + mParseJob = new ParseUserScriptJob(url); + connect(mParseJob, SIGNAL(finished(ParseUserScriptJob*)), SLOT(slotGotActiveScripts(ParseUserScriptJob*))); + mParseJob->start(); + mSieveJob = KManageSieve::SieveJob::list(url); + connect(mSieveJob, SIGNAL(gotList(KManageSieve::SieveJob*,bool,QStringList,QString)), + this, SLOT(slotGotList(KManageSieve::SieveJob*,bool,QStringList,QString))); + } else { + mSieveJob = KManageSieve::SieveJob::get(mUrl); + mSieveJob->setInteractive(false); + connect(mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), + SLOT(slotGetResult(KManageSieve::SieveJob*,bool,QString,bool))); + } +} + +void VacationCheckJob::slotGetResult(KManageSieve::SieveJob */*job*/, bool success, const QString &script, bool active) +{ + mScript = script; + mSieveCapabilities = mSieveJob->sieveCapabilities(); mSieveJob = 0; - if ( !success ) - active = false; // default to inactive - emit scriptActive( active, mServerName ); + + if (mKep14Support) { + VacationUtils::Vacation vacation = VacationUtils::parseScript(script); + if (vacation.isValid()) { + const QString &scriptName = mAvailableScripts[mScriptPos-1]; + emit scriptActive(this, scriptName, mActiveScripts.contains(scriptName) && vacation.active); + kDebug() << "vacation script found :)"; + } else if (isLastScript()) { + mNoScriptFound = true; + emit scriptActive(this, QString(), false); + kDebug() << "no vacation script found :("; + } else { + getNextScript(); + } + } else { + if ( !success ) { + active = false; // default to inactive + mNoScriptFound = true; + } + if (active) { + mActiveScripts << mUrl.fileName(); + } + emit scriptActive(this, mUrl.fileName(), active); + } +} + +void VacationCheckJob::slotGotActiveScripts(ParseUserScriptJob *job) +{ + mParseJob = 0; + if (!job->error().isEmpty()) { + emitError(QLatin1String("ParseUserScriptJob failed:")+job->error()); + return; + } + mActiveScripts = job->activeScriptList(); + + if (!mSieveJob) { + searchVacationScript(); + } +} + +void VacationCheckJob::slotGotList(KManageSieve::SieveJob *job, bool success, const QStringList &availableScripts, const QString &activeScript) +{ + mSieveJob = 0; + if (!success) { + emitError(QLatin1String("SieveJob list failed.")); + return; + } + + mAvailableScripts = availableScripts; + + if (!mParseJob) { + searchVacationScript(); + } +} + +void VacationCheckJob::emitError(const QString &errorMessage) +{ + qWarning() << errorMessage; + //TODO: emit error } + +void VacationCheckJob::searchVacationScript() +{ + QStringList scriptList = mActiveScripts; + + // Reorder script list + foreach(const QString &script, mAvailableScripts) { + if (!scriptList.contains(script)) { + scriptList.append(script); + } + } + + mAvailableScripts = scriptList; + mScriptPos = 0; + getNextScript(); +} + +void VacationCheckJob::getNextScript() +{ + if (isLastScript()) { + //TODO: no script found + mNoScriptFound = true; + emit scriptActive(this, QString(), false); + kDebug() << "no vacation script found :("; + } + KUrl url = mUrl; + url.setFileName(mAvailableScripts[mScriptPos]); + mScriptPos += 1; + if (Util::isKep14ProtectedName(url.fileName())) { + getNextScript(); + } + mSieveJob = KManageSieve::SieveJob::get(url); + mSieveJob->setInteractive(false); + connect(mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), + SLOT(slotGetResult(KManageSieve::SieveJob*,bool,QString,bool))); +} + +bool VacationCheckJob::isLastScript() const +{ + return mScriptPos >= mAvailableScripts.count(); +} + +bool VacationCheckJob::noScriptFound() +{ + return mNoScriptFound; +} + +QString VacationCheckJob::serverName() +{ + return mServerName; +} + +QString VacationCheckJob::script() +{ + return mScript; +} + +QStringList VacationCheckJob::sieveCapabilities() +{ + return mSieveCapabilities; +} + diff --git a/libksieve/ksieveui/vacation/vacationcheckjob.h b/libksieve/ksieveui/vacation/vacationcheckjob.h index a95c1cd689..3eaa8cefd0 100644 --- a/libksieve/ksieveui/vacation/vacationcheckjob.h +++ b/libksieve/ksieveui/vacation/vacationcheckjob.h @@ -1,49 +1,72 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #ifndef VACATIONCHECKJOB_H #define VACATIONCHECKJOB_H #include +#include #include namespace KManageSieve { class SieveJob; } namespace KSieveUi { +class ParseUserScriptJob; class VacationCheckJob : public QObject { Q_OBJECT public: explicit VacationCheckJob(const KUrl &url, const QString &serverName, QObject *parent=0); ~VacationCheckJob(); + void setKep14Support(bool kep14Support); + void start(); + void kill(); + bool noScriptFound(); + QString script(); + QStringList sieveCapabilities(); + QString serverName(); Q_SIGNALS: - void scriptActive(bool active, const QString &serverName); + void scriptActive(VacationCheckJob* job, const QString &sscriptName, bool active); private slots: void slotGetResult(KManageSieve::SieveJob *job, bool success, const QString &script, bool active); + void slotGotActiveScripts(ParseUserScriptJob *job); + void slotGotList(KManageSieve::SieveJob *job, bool success, const QStringList &availableScripts, const QString &activeScript); + void emitError(const QString &errorMessage); + void searchVacationScript(); + void getNextScript(); + bool isLastScript() const; private: QString mServerName; KUrl mUrl; KManageSieve::SieveJob * mSieveJob; + ParseUserScriptJob *mParseJob; + bool mKep14Support; + QStringList mAvailableScripts; + QStringList mActiveScripts; + int mScriptPos; + bool mNoScriptFound; + QString mScript; + QStringList mSieveCapabilities; }; } #endif // VACATIONCHECKJOB_H diff --git a/libksieve/ksieveui/vacation/vacationcreatescriptjob.cpp b/libksieve/ksieveui/vacation/vacationcreatescriptjob.cpp index 6d31678bba..4e76bd56b1 100644 --- a/libksieve/ksieveui/vacation/vacationcreatescriptjob.cpp +++ b/libksieve/ksieveui/vacation/vacationcreatescriptjob.cpp @@ -1,102 +1,200 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #include "vacationcreatescriptjob.h" +#include "vacationutils.h" +#include +#include #include #include #include #include using namespace KSieveUi; VacationCreateScriptJob::VacationCreateScriptJob(QObject *parent) : QObject(parent), mActivate(false), - mWasActive(false), - mSieveJob(0) + mScriptActive(false) + , mKep14Support(false) + , mUserJobRunning(false) + , mScriptJobRunning(false) + , mSuccess(true) + , mSieveJob(0) + , mParseUserJob(0) + , mCreateJob(0) { } VacationCreateScriptJob::~VacationCreateScriptJob() { + kill(); +} + +void VacationCreateScriptJob::kill() +{ + if (mSieveJob) { + mSieveJob->kill(); + } + mSieveJob = 0; + + if (mParseUserJob) { + mParseUserJob->kill(); + } + mParseUserJob = 0; + if (mCreateJob) { + mCreateJob->kill(); + } + mParseUserJob = 0; } + void VacationCreateScriptJob::setStatus(bool activate, bool wasActive) { mActivate = activate; - mWasActive = wasActive; + mScriptActive = wasActive; } void VacationCreateScriptJob::setServerName(const QString &servername) { mServerName = servername; } -void VacationCreateScriptJob::start() +const QString &VacationCreateScriptJob::serverName() const { - if (mUrl.isEmpty()) { - qDebug()<<" server url is empty"; - deleteLater(); - return; - } - mSieveJob = KManageSieve::SieveJob::put( mUrl, mScript, mActivate, mWasActive ); - if ( mActivate ) - connect( mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), - SLOT(slotPutActiveResult(KManageSieve::SieveJob*,bool)) ); - else - connect( mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), - SLOT(slotPutInactiveResult(KManageSieve::SieveJob*,bool)) ); + return mServerName; +} + +void VacationCreateScriptJob::setKep14Support(bool kep14Support) +{ + mKep14Support = kep14Support; } void VacationCreateScriptJob::setServerUrl(const KUrl &url) { mUrl = url; } void VacationCreateScriptJob::setScript(const QString &script) { mScript = script; } -void VacationCreateScriptJob::slotPutActiveResult( KManageSieve::SieveJob * job, bool success ) +void VacationCreateScriptJob::start() +{ + if (mUrl.isEmpty()) { + qDebug()<<" server url is empty"; + deleteLater(); + return; + } + + mUserJobRunning = false; + mScriptJobRunning = true; + mSieveJob = KManageSieve::SieveJob::get(mUrl); + mSieveJob->setInteractive(false); + connect(mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), + SLOT(slotGetScript(KManageSieve::SieveJob*,bool,QString,bool))); + + if (mKep14Support && mActivate && !mScriptActive) { + mUserJobRunning = true; + KUrl url = mUrl; + url.setFileName(QLatin1String("USER")); + mParseUserJob = new ParseUserScriptJob(url, this); + connect(mParseUserJob, SIGNAL(finished(ParseUserScriptJob*)), SLOT(slotGotActiveScripts(ParseUserScriptJob*))); + mParseUserJob->start(); + } +} + +void VacationCreateScriptJob::slotGetScript(KManageSieve::SieveJob *job, bool success, const QString &oldScript, bool active) { - handlePutResult( job, success, true ); + mSieveJob = 0; + QString script = mScript; + if (success || !oldScript.trimmed().isEmpty()) { + script = VacationUtils::mergeRequireLine(oldScript, mScript); + script = VacationUtils::updateVacationBlock(oldScript,mScript); + } + if (mKep14Support) { + mSieveJob = KManageSieve::SieveJob::put( mUrl, mScript, false, false ); + } else { + mSieveJob = KManageSieve::SieveJob::put( mUrl, mScript, mActivate, false ); //Never deactivate + } + connect( mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), + SLOT(slotPutResult(KManageSieve::SieveJob*,bool)) ); } -void VacationCreateScriptJob::slotPutInactiveResult( KManageSieve::SieveJob * job, bool success ) +void VacationCreateScriptJob::slotPutResult( KManageSieve::SieveJob * job, bool success ) { - handlePutResult( job, success, false ); + mSieveJob = 0; + mScriptJobRunning = false; + if (!success) { + mSuccess = false; + } + handleResult(); } -void VacationCreateScriptJob::handlePutResult( KManageSieve::SieveJob *, bool success, bool activated ) +void VacationCreateScriptJob::handleResult() { - if ( success ) - KMessageBox::information( 0, activated + if (mUserJobRunning || mScriptJobRunning) { // Not both jobs are done + return; + } + + if ( mSuccess ) + KMessageBox::information( 0, mActivate ? i18n("Sieve script installed successfully on the server \'%1\'.\n" "Out of Office reply is now active.", mServerName) : i18n("Sieve script installed successfully on the server \'%1\'.\n" "Out of Office reply has been deactivated.", mServerName) ); - kDebug() << "( ???," << success << ", ? )"; - mSieveJob = 0; // job deletes itself after returning from this slot! - Q_EMIT result( success ); - Q_EMIT scriptActive( activated, mServerName ); + kDebug() << "( ???," << mSuccess << ", ? )"; + Q_EMIT result( mSuccess ); + Q_EMIT scriptActive( mActivate, mServerName ); deleteLater(); } + +void VacationCreateScriptJob::slotGotActiveScripts(ParseUserScriptJob *job) +{ + mParseUserJob = 0; + if (!job->error().isEmpty()) { + slotGenerateDone(job->error()); + return; + } + + QStringList list = job->activeScriptList(); + + if (!list.contains(mUrl.fileName())) { + list.prepend(mUrl.fileName()); + mCreateJob = new GenerateGlobalScriptJob(mUrl, this); + mCreateJob->addUserActiveScripts(list); + connect( mCreateJob, SIGNAL(success()), SLOT(slotGenerateDone())); + connect( mCreateJob, SIGNAL(error(QString)), SLOT(slotGenerateDone(QString))); + mCreateJob->start(); + } +} + +void VacationCreateScriptJob::slotGenerateDone(const QString &error) +{ + mCreateJob = 0; + mUserJobRunning = false; + if (!error.isEmpty()) { + qWarning() << error; + mSuccess = false; + } + handleResult(); +} diff --git a/libksieve/ksieveui/vacation/vacationcreatescriptjob.h b/libksieve/ksieveui/vacation/vacationcreatescriptjob.h index c23584e78a..d5109c6e0a 100644 --- a/libksieve/ksieveui/vacation/vacationcreatescriptjob.h +++ b/libksieve/ksieveui/vacation/vacationcreatescriptjob.h @@ -1,65 +1,78 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #ifndef VACATIONCREATESCRIPTJOB_H #define VACATIONCREATESCRIPTJOB_H #include #include "ksieveui_export.h" #include namespace KManageSieve { class SieveJob; } namespace KSieveUi { +class ParseUserScriptJob; +class GenerateGlobalScriptJob; class KSIEVEUI_EXPORT VacationCreateScriptJob : public QObject { Q_OBJECT public: explicit VacationCreateScriptJob(QObject *parent=0); ~VacationCreateScriptJob(); void start(); + void kill(); void setServerUrl(const KUrl &url); void setScript(const QString &script); void setServerName(const QString &servername); + const QString &serverName() const; void setStatus(bool activate, bool wasActive); + void setKep14Support(bool kep14Support); Q_SIGNALS: void result(bool); void scriptActive(bool activated, const QString &serverName); private slots: - void slotPutActiveResult(KManageSieve::SieveJob *job, bool success); - void slotPutInactiveResult(KManageSieve::SieveJob *job, bool success); + void slotPutResult(KManageSieve::SieveJob *job, bool success); + void slotGetScript(KManageSieve::SieveJob *job, bool success, const QString &oldScript, bool active); + void slotGotActiveScripts(ParseUserScriptJob *job); + void slotGenerateDone(const QString &error=QString()); private: - void handlePutResult(KManageSieve::SieveJob *, bool success, bool activated); + void handleResult(); KUrl mUrl; QString mScript; QString mServerName; bool mActivate; - bool mWasActive; + bool mScriptActive; + bool mKep14Support; + bool mUserJobRunning; + bool mScriptJobRunning; + bool mSuccess; KManageSieve::SieveJob *mSieveJob; + ParseUserScriptJob *mParseUserJob; + GenerateGlobalScriptJob *mCreateJob; }; } #endif // VACATIONCREATESCRIPTJOB_H diff --git a/libksieve/ksieveui/vacation/vacationdialog.cpp b/libksieve/ksieveui/vacation/vacationdialog.cpp index 951f663ee4..ba8226965a 100644 --- a/libksieve/ksieveui/vacation/vacationdialog.cpp +++ b/libksieve/ksieveui/vacation/vacationdialog.cpp @@ -1,200 +1,235 @@ /* vacationdialog.cpp Copyright (c) 2013, 2014 Montel Laurent Copyright (c) 2002 Marc Mutz This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. 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, US */ #include "vacationdialog.h" #include "vacationeditwidget.h" #include #include #include #include #include #include #include #include #include using KMime::Types::AddrSpecList; using namespace KSieveUi; VacationDialog::VacationDialog( const QString &caption, QWidget * parent, bool modal ) : KDialog( parent ) { setCaption( caption ); setButtons( Ok|Cancel|Default ); setDefaultButton( Ok ); setModal( modal ); QWidget *w = new QWidget; QVBoxLayout *vbox = new QVBoxLayout; vbox->setMargin(0); w->setLayout(vbox); vbox->addWidget(mVacationEditWidget); mVacationEditWidget = new VacationEditWidget; KSeparator *separator = new KSeparator; vbox->addWidget(separator); setMainWidget( w ); KWindowSystem::setIcons( winId(), qApp->windowIcon().pixmap(IconSize(KIconLoader::Desktop),IconSize(KIconLoader::Desktop)), qApp->windowIcon().pixmap(IconSize(KIconLoader::Small),IconSize(KIconLoader::Small)) ); readConfig(); connect( this, SIGNAL(defaultClicked()), this, SLOT(slotDialogDefaults()) ); } VacationDialog::~VacationDialog() { kDebug() << "~VacationDialog()"; writeConfig(); } void VacationDialog::writeConfig() { KConfigGroup group( KGlobal::config(), "VacationDialog" ); group.writeEntry( "Size", size() ); } void VacationDialog::readConfig() { KConfigGroup group( KGlobal::config(), "VacationDialog" ); const QSize size = group.readEntry( "Size", QSize() ); if ( size.isValid() ) { resize( size ); } else { resize( sizeHint().width(), sizeHint().height() ); } } bool VacationDialog::activateVacation() const { return mVacationEditWidget->activateVacation(); } void VacationDialog::setActivateVacation( bool activate ) { mVacationEditWidget->setActivateVacation(activate); } QString VacationDialog::messageText() const { return mVacationEditWidget->messageText(); } void VacationDialog::setMessageText( const QString &text ) { mVacationEditWidget->setMessageText(text); } QString VacationDialog::subject() const { return mVacationEditWidget->subject(); } void VacationDialog::setSubject(const QString &subject) { return mVacationEditWidget->setSubject(subject); } +VacationUtils::MailAction VacationDialog::mailAction() const +{ + return mVacationEditWidget->mailAction(); +} + +QString VacationDialog::mailActionRecipient() const +{ + return mVacationEditWidget->mailActionRecipient(); +} + +void VacationDialog::setMailAction(VacationUtils::MailAction action, const QString& recipient) +{ + mVacationEditWidget->setMailAction(action, recipient); +} + int VacationDialog::notificationInterval() const { return mVacationEditWidget->notificationInterval(); } void VacationDialog::setNotificationInterval( int days ) { mVacationEditWidget->setNotificationInterval(days); } AddrSpecList VacationDialog::mailAliases() const { return mVacationEditWidget->mailAliases(); } void VacationDialog::setMailAliases( const AddrSpecList &aliases ) { mVacationEditWidget->setMailAliases(aliases); } void VacationDialog::setMailAliases( const QString &aliases ) { mVacationEditWidget->setMailAliases(aliases); } QString VacationDialog::domainName() const { return mVacationEditWidget->domainName(); } void VacationDialog::setDomainName( const QString &domain ) { mVacationEditWidget->setDomainName(domain); } bool VacationDialog::domainCheck() const { return mVacationEditWidget->domainCheck(); } void VacationDialog::setDomainCheck( bool check ) { mVacationEditWidget->setDomainCheck(check); } bool VacationDialog::sendForSpam() const { return mVacationEditWidget->sendForSpam(); } void VacationDialog::setSendForSpam( bool enable ) { mVacationEditWidget->setSendForSpam(enable); } void VacationDialog::enableDomainAndSendForSpam( bool enable ) { mVacationEditWidget->enableDomainAndSendForSpam(enable); } void VacationDialog::slotDialogDefaults() { mVacationEditWidget->setDefault(); } void VacationDialog::enableDates(bool enable) { mVacationEditWidget->enableDates(enable); } QDate VacationDialog::endDate() const { return mVacationEditWidget->endDate(); } void VacationDialog::setEndDate( const QDate &endDate ) { mVacationEditWidget->setEndDate( endDate ); } +QTime VacationDialog::endTime() const +{ + return mVacationEditWidget->endTime(); +} + +void VacationDialog::setEndTime(const QTime &endTime) +{ + mVacationEditWidget->setEndTime(endTime); +} + QDate VacationDialog::startDate() const { return mVacationEditWidget->startDate(); } void VacationDialog::setStartDate( const QDate &startDate ) { mVacationEditWidget->setStartDate( startDate ); } + +QTime VacationDialog::startTime() const +{ + return mVacationEditWidget->startTime(); +} + +void VacationDialog::setStartTime(const QTime &startTime) +{ + mVacationEditWidget->setStartTime(startTime); +} diff --git a/libksieve/ksieveui/vacation/vacationdialog.h b/libksieve/ksieveui/vacation/vacationdialog.h index 02fb0faa17..285d6dac9e 100644 --- a/libksieve/ksieveui/vacation/vacationdialog.h +++ b/libksieve/ksieveui/vacation/vacationdialog.h @@ -1,89 +1,99 @@ /* -*- c++ -*- vacationdialog.h Copyright (c) 2002 Marc Mutz This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. 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, US */ #ifndef KSIEVE_KSIEVEUI_VACATIONDIALOG_H #define KSIEVE_KSIEVEUI_VACATIONDIALOG_H #include +#include "vacationutils.h" template class QList; class KDateTime; namespace KMime { namespace Types { struct AddrSpec; typedef QList AddrSpecList; } } namespace KSieveUi { class VacationEditWidget; class VacationDialog : public KDialog { Q_OBJECT public: explicit VacationDialog( const QString &caption, QWidget * parent=0, bool modal=true ); ~VacationDialog(); void enableDomainAndSendForSpam( bool enable = true ); bool activateVacation() const; void setActivateVacation( bool activate ); bool domainCheck() const; void setDomainCheck( bool check ); QString messageText() const; void setMessageText( const QString &text ); QString subject() const; void setSubject( const QString &subject ); + VacationUtils::MailAction mailAction() const; + QString mailActionRecipient() const; + void setMailAction (KSieveUi::VacationUtils::MailAction action, const QString &recipient); + int notificationInterval() const; void setNotificationInterval( int days ); KMime::Types::AddrSpecList mailAliases() const; void setMailAliases( const KMime::Types::AddrSpecList & aliases ); void setMailAliases( const QString &aliases ); QString domainName() const; void setDomainName( const QString &domain ); bool sendForSpam() const; void setSendForSpam( bool enable ); void enableDates( bool enable = true ); QDate startDate() const; void setStartDate( const QDate &startDate ); + QTime startTime() const; + void setStartTime( const QTime &startTime ); + QDate endDate() const; void setEndDate( const QDate &endDate ); + QTime endTime() const; + void setEndTime( const QTime &endTime ); private slots: void slotDialogDefaults(); private: void writeConfig(); void readConfig(); VacationEditWidget *mVacationEditWidget; }; } #endif diff --git a/libksieve/ksieveui/vacation/vacationeditwidget.cpp b/libksieve/ksieveui/vacation/vacationeditwidget.cpp index 642f1f24de..72782eebd4 100644 --- a/libksieve/ksieveui/vacation/vacationeditwidget.cpp +++ b/libksieve/ksieveui/vacation/vacationeditwidget.cpp @@ -1,323 +1,437 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #include "vacationeditwidget.h" #include "vacationutils.h" #include #include #include #include #include #include #include #include #include #include +#include #include +#include #include using KMime::Types::AddrSpecList; using KMime::Types::AddressList; using KMime::Types::MailboxList; using KMime::HeaderParsing::parseAddressList; using namespace KSieveUi; VacationEditWidget::VacationEditWidget(QWidget *parent) : QWidget(parent) { int row = -1; QGridLayout * glay = new QGridLayout( this ); glay->setSpacing( KDialog::spacingHint() ); glay->setMargin( 0 ); glay->setColumnStretch( 1, 1 ); // explanation label: ++row; glay->addWidget( new QLabel( i18n("Configure vacation " "notifications to be sent:"), this ), row, 0, 1, 2 ); // Activate checkbox: ++row; mActiveCheck = new QCheckBox( i18n("&Activate vacation notifications"), this ); glay->addWidget( mActiveCheck, row, 0, 1, 2 ); // Message text edit: ++row; glay->setRowStretch( row, 1 ); mTextEdit = new PimCommon::RichTextEditorWidget( this ); mTextEdit->setObjectName( QLatin1String("mTextEdit") ); mTextEdit->setAcceptRichText( false ); glay->addWidget( mTextEdit, row, 0, 1, 2 ); // Subject ++row; mSubject = new KLineEdit(this); mSubject->setObjectName(QLatin1String("mSubject")); mSubject->setClearButtonShown(true); QLabel *tmpLabel = new QLabel(i18n("&Subject of the vacation mail:"), this); tmpLabel->setBuddy(mSubject); glay->addWidget(tmpLabel, row, 0); glay->addWidget(mSubject, row, 1); // From date - ++row; + + QHBoxLayout *timeLayout = new QHBoxLayout(this); + mStartDate = new KDateComboBox( this ); mStartDate->setObjectName( QLatin1String( "mStartDate" ) ); mStartDate->setOptions( KDateComboBox::EditDate | KDateComboBox::SelectDate | KDateComboBox::DatePicker | KDateComboBox::DateKeywords | KDateComboBox::WarnOnInvalid ); mStartDate->setEnabled( false ); // Disable by default - we need an extension to support this + + mStartTime = new KTimeComboBox( this ); + mStartTime->setObjectName( QLatin1String( "mStartTime" ) ); + mStartTime->setOptions( KTimeComboBox::EditTime | KTimeComboBox::SelectTime | KTimeComboBox::EditTime | KTimeComboBox::WarnOnInvalid ); + mStartTime->setEnabled( false ); // Disable by default - we need an extension to support this + + mStartTimeActive = new QCheckBox(this); + connect(mStartTimeActive,SIGNAL(toggled(bool)), mStartTime, SLOT(setEnabled(bool))); + + timeLayout->addWidget(mStartDate); + timeLayout->addWidget(mStartTimeActive); + timeLayout->addWidget(mStartTime); + + ++row; QLabel *label = new QLabel( i18n("&Start:"), this ); label->setBuddy( mStartDate ); glay->addWidget( label, row, 0 ); - glay->addWidget( mStartDate, row, 1 ); + glay->addLayout(timeLayout, row, 1); // End date - ++row; + timeLayout = new QHBoxLayout(this); + mEndDate = new KDateComboBox( this ); mEndDate->setObjectName( QLatin1String( "mEndDate" ) ); mEndDate->setOptions( KDateComboBox::EditDate | KDateComboBox::SelectDate | KDateComboBox::DatePicker | KDateComboBox::DateKeywords | KDateComboBox::WarnOnInvalid ); mEndDate->setEnabled( false ); // Disable by default - we need an extension to support this + + mEndTime = new KTimeComboBox( this ); + mEndTime->setObjectName( QLatin1String( "mEndTime" ) ); + mEndTime->setOptions( KTimeComboBox::EditTime | KTimeComboBox::SelectTime | KTimeComboBox::EditTime | KTimeComboBox::WarnOnInvalid ); + mEndTime->setEnabled( false ); // Disable by default - we need an extension to support this + + mEndTimeActive = new QCheckBox(this); + connect(mEndTimeActive,SIGNAL(toggled(bool)), mEndTime, SLOT(setEnabled(bool))); + + timeLayout->addWidget(mEndDate); + timeLayout->addWidget(mEndTimeActive); + timeLayout->addWidget(mEndTime); + + ++row; label = new QLabel( i18n("&End:"), this ); label->setBuddy( mEndDate ); glay->addWidget( label, row, 0 ); - glay->addWidget( mEndDate, row, 1 ); + glay->addLayout(timeLayout, row, 1); // "Resent only after" spinbox and label: ++row; int defDayInterval = 7; //default day interval - mIntervalSpin = new KIntSpinBox( 1, 356, 1, defDayInterval, this ); + mIntervalSpin = new KIntSpinBox( 0, 356, 1, defDayInterval, this ); mIntervalSpin->setObjectName( QLatin1String("mIntervalSpin") ); mIntervalSpin->setSuffix( i18np(" day", " days", defDayInterval) ); connect(mIntervalSpin, SIGNAL(valueChanged(int)), SLOT(slotIntervalSpinChanged(int)) ); label = new QLabel( i18n("&Resend notification only after:"), this ); label->setBuddy( mIntervalSpin ); glay->addWidget( label, row, 0 ); glay->addWidget( mIntervalSpin, row, 1 ); // "Send responses for these addresses" lineedit and label: ++row; mMailAliasesEdit = new KLineEdit( this ); mMailAliasesEdit->setObjectName( QLatin1String("mMailAliasesEdit") ); mMailAliasesEdit->setClearButtonShown( true ); tmpLabel = new QLabel( i18n("&Send responses for these addresses:"), this ); tmpLabel->setBuddy( mMailAliasesEdit ); glay->addWidget( tmpLabel, row, 0 ); glay->addWidget( mMailAliasesEdit, row, 1 ); + // Action for incomming mails + mMailAction = new QComboBox(this); + for (int i=0; i<4; i++) { + mMailAction->addItem(VacationUtils::mailAction((VacationUtils::MailAction) i)); + } + mMailAction->setObjectName(QLatin1String("mMailAction")); + connect(mMailAction, SIGNAL(currentIndexChanged(int)), SLOT(mailActionChanged(int))); + + mMailActionRecipient = new KLineEdit(this); + mMailActionRecipient->setObjectName(QLatin1String("mMailActionRecipient")); + mMailActionRecipient->setClearButtonShown(true); + mMailActionRecipient->setEnabled(false); + + QHBoxLayout *hLayout = new QHBoxLayout(this); + + hLayout->addWidget(mMailAction); + hLayout->addWidget(mMailActionRecipient); + + ++row; + tmpLabel = new QLabel(i18n("&Action for incomming mails:"), this); + tmpLabel->setBuddy(mMailAction); + glay->addWidget(tmpLabel, row, 0); + glay->addLayout(hLayout, row, 1); + // "Send responses also to SPAM mail" checkbox: ++row; mSpamCheck = new QCheckBox( i18n("Do not send vacation replies to spam messages"), this ); mSpamCheck->setObjectName( QLatin1String("mSpamCheck") ); mSpamCheck->setChecked( true ); glay->addWidget( mSpamCheck, row, 0, 1, 2 ); // domain checkbox and linedit: ++row; mDomainCheck = new QCheckBox( i18n("Only react to mail coming from domain"), this ); mDomainCheck->setObjectName( QLatin1String("mDomainCheck") ); mDomainCheck->setChecked( false ); mDomainEdit = new KLineEdit( this ); mDomainEdit->setObjectName( QLatin1String("mDomainEdit") ); mDomainEdit->setClearButtonShown( true ); mDomainEdit->setEnabled( false ); mDomainEdit->setValidator( new QRegExpValidator( QRegExp( QLatin1String("[a-zA-Z0-9+-]+(?:\\.[a-zA-Z0-9+-]+)*") ), mDomainEdit ) ); glay->addWidget( mDomainCheck, row, 0 ); glay->addWidget( mDomainEdit, row, 1 ); connect( mDomainCheck, SIGNAL(toggled(bool)), mDomainEdit, SLOT(setEnabled(bool)) ); } VacationEditWidget::~VacationEditWidget() { } bool VacationEditWidget::activateVacation() const { return mActiveCheck->isChecked(); } void VacationEditWidget::setActivateVacation( bool activate ) { mActiveCheck->setChecked( activate ); } QString VacationEditWidget::messageText() const { return mTextEdit->toPlainText().trimmed(); } void VacationEditWidget::setMessageText( const QString &text ) { mTextEdit->setPlainText( text ); const int height = ( mTextEdit->fontMetrics().lineSpacing() + 1 ) * 11; mTextEdit->setMinimumHeight( height ); } int VacationEditWidget::notificationInterval() const { return mIntervalSpin->value(); } void VacationEditWidget::setNotificationInterval( int days ) { mIntervalSpin->setValue( days ); } AddrSpecList VacationEditWidget::mailAliases() const { QByteArray text = mMailAliasesEdit->text().toLatin1(); // ### IMAA: !ok AddressList al; const char * s = text.begin(); parseAddressList( s, text.end(), al ); AddrSpecList asl; AddressList::const_iterator end(al.constEnd()); for ( AddressList::const_iterator it = al.constBegin() ; it != end; ++it ) { const MailboxList & mbl = (*it).mailboxList; for ( MailboxList::const_iterator jt = mbl.constBegin() ; jt != mbl.constEnd() ; ++jt ) asl.push_back( (*jt).addrSpec() ); } return asl; } void VacationEditWidget::setMailAliases( const AddrSpecList &aliases ) { QStringList sl; AddrSpecList::const_iterator end(aliases.constEnd()); for ( AddrSpecList::const_iterator it = aliases.constBegin() ; it != end; ++it ) sl.push_back( (*it).asString() ); mMailAliasesEdit->setText( sl.join(QLatin1String(", ")) ); } void VacationEditWidget::setMailAliases( const QString &aliases ) { mMailAliasesEdit->setText( aliases ); } void VacationEditWidget::slotIntervalSpinChanged ( int value ) { mIntervalSpin->setSuffix( i18np(" day", " days", value) ); } QString VacationEditWidget::domainName() const { return mDomainCheck->isChecked() ? mDomainEdit->text() : QString() ; } void VacationEditWidget::setDomainName( const QString &domain ) { if ( !domain.isEmpty() ) { mDomainEdit->setText( domain ); mDomainCheck->setChecked( true ); } } bool VacationEditWidget::domainCheck() const { return mDomainCheck->isChecked(); } void VacationEditWidget::setDomainCheck( bool check ) { mDomainCheck->setChecked( check ); } bool VacationEditWidget::sendForSpam() const { return !mSpamCheck->isChecked(); } void VacationEditWidget::setSendForSpam( bool enable ) { mSpamCheck->setChecked( !enable ); } -QDate VacationEditWidget::endDate() +QDate VacationEditWidget::endDate() const { if ( mEndDate->isEnabled() ) { return mEndDate->date(); } else { return QDate(); } } void VacationEditWidget::setEndDate( const QDate &endDate ) { mEndDate->setDate( endDate ); } +QTime VacationEditWidget::endTime() const +{ + if ( mEndTime->isEnabled() ) { + return mEndTime->time(); + } else { + return QTime(); + } +} + +void VacationEditWidget::setEndTime( const QTime &endTime ) +{ + mEndTimeActive->setChecked(endTime.isValid()); + mEndTime->setEnabled(endTime.isValid()); + mEndTime->setTime( endTime ); +} + QDate VacationEditWidget::startDate() const { if ( mStartDate->isEnabled() ) { return mStartDate->date(); } else { return QDate(); } } void VacationEditWidget::setStartDate( const QDate &startDate ) { mStartDate->setDate( startDate ); } +QTime VacationEditWidget::startTime() const +{ + if ( mStartTime->isEnabled() ) { + return mStartTime->time(); + } else { + return QTime(); + } +} + +void VacationEditWidget::setStartTime( const QTime &startTime ) +{ + mStartTimeActive->setChecked(startTime.isValid()); + mStartTime->setEnabled(startTime.isValid()); + mStartTime->setTime( startTime ); +} + void VacationEditWidget::setSubject(const QString &subject) { mSubject->setText(subject); } QString VacationEditWidget::subject() const { if (mSubject->isEnabled()) { return mSubject->text(); } else { return QString(); } } +void VacationEditWidget::mailActionChanged(int action) +{ + bool enable = (action == VacationUtils::CopyTo || action == VacationUtils::Sendto); + mMailActionRecipient->setEnabled(enable); +} + + +void VacationEditWidget::setMailAction(VacationUtils::MailAction action, const QString &recipient) +{ + mMailAction->setCurrentIndex(action); + mMailActionRecipient->setText(recipient); +} + +VacationUtils::MailAction VacationEditWidget::mailAction() const +{ + return (VacationUtils::MailAction) mMailAction->currentIndex(); +} + +QString VacationEditWidget::mailActionRecipient() const +{ + return mMailActionRecipient->text(); +} + + void VacationEditWidget::enableDates( bool enable ) { mStartDate->setEnabled( enable ); mEndDate->setEnabled( enable ); } void VacationEditWidget::enableDomainAndSendForSpam( bool enable ) { mDomainCheck->setEnabled( enable ); mDomainEdit->setEnabled( enable && mDomainCheck->isChecked() ); mSpamCheck->setEnabled( enable ); } void VacationEditWidget::setDefault() { setActivateVacation( true ); setMessageText( VacationUtils::defaultMessageText() ); setSubject(VacationUtils::defaultSubject()); setNotificationInterval( VacationUtils::defaultNotificationInterval() ); - setMailAliases( VacationUtils::defaultMailAliases().join(QLatin1String(", ")) ); + setMailAliases( VacationUtils::defaultMailAliases() ); setSendForSpam( VacationUtils::defaultSendForSpam() ); setDomainName( VacationUtils::defaultDomainName() ); + setMailAction(VacationUtils::defaultMailAction(), QString()); setDomainCheck( false ); } diff --git a/libksieve/ksieveui/vacation/vacationeditwidget.h b/libksieve/ksieveui/vacation/vacationeditwidget.h index df70d73f18..7b0f1380b1 100644 --- a/libksieve/ksieveui/vacation/vacationeditwidget.h +++ b/libksieve/ksieveui/vacation/vacationeditwidget.h @@ -1,104 +1,125 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #ifndef VACATIONEDITWIDGET_H #define VACATIONEDITWIDGET_H #include +#include "vacationutils.h" class KIntSpinBox; class KLineEdit; class KDateComboBox; +class KTimeComboBox; +class QComboBox; class QDate; +class QTime; namespace PimCommon { class RichTextEditorWidget; } class QCheckBox; template class QList; namespace KMime { namespace Types { struct AddrSpec; typedef QList AddrSpecList; } } namespace KSieveUi { class VacationEditWidget : public QWidget { Q_OBJECT public: explicit VacationEditWidget(QWidget *parent=0); ~VacationEditWidget(); void enableDates( bool enable = true ); void enableDomainAndSendForSpam( bool enable = true ); bool activateVacation() const; void setActivateVacation( bool activate ); bool domainCheck() const; void setDomainCheck( bool check ); QString messageText() const; void setMessageText( const QString &text ); int notificationInterval() const; void setNotificationInterval( int days ); KMime::Types::AddrSpecList mailAliases() const; void setMailAliases( const KMime::Types::AddrSpecList & aliases ); void setMailAliases( const QString &aliases ); QString domainName() const; void setDomainName( const QString &domain ); QString subject() const; void setSubject(const QString &subject); bool sendForSpam() const; void setSendForSpam( bool enable ); QDate startDate() const; void setStartDate( const QDate &startDate ); - QDate endDate(); + QTime startTime() const; + void setStartTime( const QTime &startTime ); + + QDate endDate() const; void setEndDate( const QDate &endDate ); + QTime endTime() const; + void setEndTime( const QTime &endTime ); + + VacationUtils::MailAction mailAction() const; + QString mailActionRecipient() const; + void setMailAction(VacationUtils::MailAction action, const QString &recipient); + void setDefault(); private Q_SLOTS: void slotIntervalSpinChanged( int value ); + void mailActionChanged(int index); protected: QCheckBox *mActiveCheck; KIntSpinBox *mIntervalSpin; KLineEdit *mMailAliasesEdit; PimCommon::RichTextEditorWidget *mTextEdit; QCheckBox *mSpamCheck; QCheckBox *mDomainCheck; KLineEdit *mDomainEdit; KLineEdit *mSubject; + QComboBox *mMailAction; + KLineEdit *mMailActionRecipient; KDateComboBox *mStartDate; + KTimeComboBox *mStartTime; + QCheckBox *mStartTimeActive; KDateComboBox *mEndDate; + KTimeComboBox *mEndTime; + QCheckBox *mEndTimeActive; }; } #endif // VACATIONEDITWIDGET_H diff --git a/libksieve/ksieveui/vacation/vacationmanager.cpp b/libksieve/ksieveui/vacation/vacationmanager.cpp index ea93204d17..32c92eaa27 100644 --- a/libksieve/ksieveui/vacation/vacationmanager.cpp +++ b/libksieve/ksieveui/vacation/vacationmanager.cpp @@ -1,87 +1,103 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #include "vacationmanager.h" #include "ksieveui/vacation/multiimapvacationmanager.h" #include "ksieveui/vacation/multiimapvacationdialog.h" #include "ksieveui/vacation/vacationcreatescriptjob.h" #include #include #include using namespace KSieveUi; VacationManager::VacationManager(QWidget *parent) : QObject(parent), mWidget(parent) + , mMultiImapVacationDialog(0) + , mQuestionAsked(false) { + mCheckVacation = new KSieveUi::MultiImapVacationManager( this ); + connect( mCheckVacation, SIGNAL(scriptActive(bool,QString)), SIGNAL(updateVacationScriptStatus(bool,QString)) ); + connect( mCheckVacation, SIGNAL(scriptActive(bool,QString)), SLOT(slotUpdateVacationScriptStatus(bool,QString)) ); } VacationManager::~VacationManager() { + mCheckVacation = 0; + mMultiImapVacationDialog = 0; } void VacationManager::checkVacation() { - delete mCheckVacation; - - mCheckVacation = new KSieveUi::MultiImapVacationManager( this ); - connect( mCheckVacation, SIGNAL(scriptActive(bool,QString)), SIGNAL(updateVacationScriptStatus(bool,QString)) ); - connect( mCheckVacation, SIGNAL(requestEditVacation()), SIGNAL(editVacation()) ); mCheckVacation->checkVacation(); } +void VacationManager::slotUpdateVacationScriptStatus(bool active, const QString &serverName) +{ + if (active) { + if (!mQuestionAsked) { + mQuestionAsked = true; + if ( KMessageBox::questionYesNo( 0, i18n( "There is still an active out-of-office reply configured.\n" + "Do you want to edit it?"), i18n("Out-of-office reply still active"), + KGuiItem( i18n( "Edit"), QLatin1String("document-properties") ), + KGuiItem( i18n("Ignore"), QLatin1String("dialog-cancel") ) ) + == KMessageBox::Yes ) { + slotEditVacation(serverName); + } + } + } +} + + void VacationManager::slotEditVacation(const QString &serverName) { if ( mMultiImapVacationDialog ) { - mMultiImapVacationDialog->show(); mMultiImapVacationDialog->raise(); mMultiImapVacationDialog->activateWindow(); - if (!serverName.isEmpty()) { - mMultiImapVacationDialog->switchToServerNamePage(serverName); - } - return; + } else { + mMultiImapVacationDialog = new KSieveUi::MultiImapVacationDialog(mCheckVacation, mWidget); + connect( mMultiImapVacationDialog, SIGNAL(okClicked()), SLOT(slotDialogOk()) ); + connect( mMultiImapVacationDialog, SIGNAL(cancelClicked()), SLOT(slotDialogCanceled()) ); } - mMultiImapVacationDialog = new KSieveUi::MultiImapVacationDialog(mWidget); - connect( mMultiImapVacationDialog, SIGNAL(okClicked()), SLOT(slotDialogOk()) ); - connect( mMultiImapVacationDialog, SIGNAL(cancelClicked()), SLOT(slotDialogCanceled()) ); mMultiImapVacationDialog->show(); if (!serverName.isEmpty()) { mMultiImapVacationDialog->switchToServerNamePage(serverName); } } void VacationManager::slotDialogCanceled() { mMultiImapVacationDialog->delayedDestruct(); mMultiImapVacationDialog = 0; } void VacationManager::slotDialogOk() { QList listJob = mMultiImapVacationDialog->listCreateJob(); Q_FOREACH (KSieveUi::VacationCreateScriptJob *job, listJob) { connect(job, SIGNAL(scriptActive(bool,QString)), SIGNAL(updateVacationScriptStatus(bool,QString))); + job->setKep14Support(mCheckVacation->kep14Support(job->serverName())); job->start(); } mMultiImapVacationDialog->delayedDestruct(); mMultiImapVacationDialog = 0; } diff --git a/libksieve/ksieveui/vacation/vacationmanager.h b/libksieve/ksieveui/vacation/vacationmanager.h index 55fc540da1..5f2678e8a8 100644 --- a/libksieve/ksieveui/vacation/vacationmanager.h +++ b/libksieve/ksieveui/vacation/vacationmanager.h @@ -1,58 +1,60 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #ifndef VACATIONMANAGER_H #define VACATIONMANAGER_H #include #include "ksieveui_export.h" #include class QWidget; namespace KSieveUi { class MultiImapVacationManager; class MultiImapVacationDialog; class KSIEVEUI_EXPORT VacationManager : public QObject { Q_OBJECT public: explicit VacationManager(QWidget *parent); ~VacationManager(); void checkVacation(); public Q_SLOTS: void slotEditVacation(const QString &serverName); Q_SIGNALS: - void updateVacationScriptStatus(bool, const QString&); + void updateVacationScriptStatus(bool active, const QString &serverName); void editVacation(); private slots: void slotDialogCanceled(); void slotDialogOk(); + void slotUpdateVacationScriptStatus(bool active, const QString &serverName); private: QPointer mMultiImapVacationDialog; QPointer mCheckVacation; QWidget *mWidget; + bool mQuestionAsked; }; } #endif // VACATIONMANAGER_H diff --git a/libksieve/ksieveui/vacation/vacationpagewidget.cpp b/libksieve/ksieveui/vacation/vacationpagewidget.cpp index dbc42ae74a..e6c7e51337 100644 --- a/libksieve/ksieveui/vacation/vacationpagewidget.cpp +++ b/libksieve/ksieveui/vacation/vacationpagewidget.cpp @@ -1,179 +1,192 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #include "vacationpagewidget.h" #include "vacationeditwidget.h" #include "vacationwarningwidget.h" #include "vacationcreatescriptjob.h" #include "vacationutils.h" +#include "multiimapvacationmanager.h" +#include #include "sieve-vacation.h" #include #include #include #include #include #include #include using namespace KSieveUi; VacationPageWidget::VacationPageWidget(QWidget *parent) : QWidget(parent), mPageScript(Script), - mSieveJob(0), mWasActive(false) { QVBoxLayout *lay = new QVBoxLayout; lay->setMargin(0); mStackWidget = new QStackedWidget; lay->addWidget(mStackWidget); //Main Page QWidget *mainPage = new QWidget; QVBoxLayout *vbox = new QVBoxLayout; mainPage->setLayout(vbox); mVacationWarningWidget = new VacationWarningWidget; vbox->addWidget(mVacationWarningWidget); mVacationEditWidget = new VacationEditWidget; vbox->addWidget(mVacationEditWidget); mStackWidget->addWidget(mainPage); QWidget *w = new QWidget; vbox = new QVBoxLayout; QLabel *lab = new QLabel(i18n( "Your server did not list \"vacation\" in " "its list of supported Sieve extensions;" "without it, KMail cannot install out-of-" "office replies for you." "Please contact your system administrator." ) ); vbox->addWidget(lab); vbox->setAlignment(lab, Qt::AlignVCenter); lab->setWordWrap(true); w->setLayout(vbox); mStackWidget->addWidget(w); mStackWidget->setCurrentIndex(Script); setLayout(lay); } VacationPageWidget::~VacationPageWidget() { - if ( mSieveJob ) - mSieveJob->kill(); - mSieveJob = 0; } void VacationPageWidget::setServerUrl(const KUrl &url) { mUrl = url; mVacationEditWidget->setEnabled(false); - mSieveJob = KManageSieve::SieveJob::get( url ); - connect( mSieveJob, SIGNAL(gotScript(KManageSieve::SieveJob*,bool,QString,bool)), - SLOT(slotGetResult(KManageSieve::SieveJob*,bool,QString,bool)) ); +} + +void VacationPageWidget::setVacationManager(MultiImapVacationManager *vacationManager) +{ + mVacationManager = vacationManager; + connect(mVacationManager, SIGNAL(scriptAvailable(QString,QStringList,QString,QString,bool)), + SLOT(slotGetResult(QString,QStringList,QString,QString,bool))); + mVacationManager->checkVacation(mServerName, mUrl); } void VacationPageWidget::setServerName(const QString &serverName) { mServerName = serverName; } -void VacationPageWidget::slotGetResult( KManageSieve::SieveJob * job, bool success, const QString & script, bool active ) +void VacationPageWidget::slotGetResult(const QString &serverName, const QStringList &sieveCapabilities, const QString &scriptName, const QString &script, bool active) { - kDebug() << success - << ", ?," << active << ")" << endl + if (serverName != mServerName) { + return; + } + + kDebug() << serverName << sieveCapabilities << endl + << scriptName << "(" << active << ")" << endl << "script:" << endl << script; - mSieveJob = 0; // job deletes itself after returning from this slot! if ( mUrl.protocol() == QLatin1String("sieve") && - !job->sieveCapabilities().contains(QLatin1String("vacation")) ) { + !sieveCapabilities.contains(QLatin1String("vacation")) ) { mStackWidget->setCurrentIndex(ScriptNotSupported); return; } + mUrl.setFileName(scriptName); + // Whether the server supports the "date" extension - const bool supportsSieveDate = mUrl.protocol() == QLatin1String("sieve") && job->sieveCapabilities().contains(QLatin1String("date")); + const bool supportsSieveDate = mUrl.protocol() == QLatin1String("sieve") && sieveCapabilities.contains(QLatin1String("date")); - mVacationEditWidget->setEnabled(true); - QString messageText = VacationUtils::defaultMessageText(); - QString subject = VacationUtils::defaultSubject(); - int notificationInterval = VacationUtils::defaultNotificationInterval(); - QStringList aliases = VacationUtils::defaultMailAliases(); - bool sendForSpam = VacationUtils::defaultSendForSpam(); - QString domainName = VacationUtils::defaultDomainName(); - QDate startDate = VacationUtils::defaultStartDate(); - QDate endDate = VacationUtils::defaultEndDate(); - if ( !success ) - active = false; // default to inactive - - if ( ( !success || !KSieveUi::VacationUtils::parseScript( script, messageText, subject, notificationInterval, aliases, sendForSpam, domainName, startDate, endDate ) ) ) + KSieveUi::VacationUtils::Vacation vacation = KSieveUi::VacationUtils::parseScript(script); + + if (!vacation.isValid() && !script.trimmed().isEmpty() ) { mVacationWarningWidget->setVisible(true); + } mWasActive = active; - mVacationEditWidget->setActivateVacation( active ); - mVacationEditWidget->setMessageText( messageText ); - mVacationEditWidget->setSubject( subject ); - mVacationEditWidget->setNotificationInterval( notificationInterval ); - mVacationEditWidget->setMailAliases( aliases.join(QLatin1String(", ")) ); - mVacationEditWidget->setSendForSpam( sendForSpam ); - mVacationEditWidget->setDomainName( domainName ); + mVacationEditWidget->setEnabled(true); + mVacationEditWidget->setActivateVacation( active && vacation.active ); + mVacationEditWidget->setMessageText( vacation.messageText ); + mVacationEditWidget->setSubject( vacation.subject ); + mVacationEditWidget->setMailAction(vacation.mailAction, vacation.mailActionRecipient); + mVacationEditWidget->setNotificationInterval( vacation.notificationInterval ); + mVacationEditWidget->setMailAliases( vacation.aliases ); + mVacationEditWidget->setSendForSpam( vacation.sendForSpam ); + mVacationEditWidget->setDomainName( vacation.excludeDomain ); mVacationEditWidget->enableDomainAndSendForSpam( !VacationSettings::allowOutOfOfficeUploadButNoSettings() ); mVacationEditWidget->enableDates( supportsSieveDate ); if ( supportsSieveDate ) { - mVacationEditWidget->setStartDate( startDate ); - mVacationEditWidget->setEndDate( endDate ); + mVacationEditWidget->setStartDate( vacation.startDate ); + mVacationEditWidget->setStartTime(vacation.startTime); + mVacationEditWidget->setEndDate( vacation.endDate ); + mVacationEditWidget->setEndTime(vacation.endTime); } //emit scriptActive( mWasActive, mServerName ); } + + KSieveUi::VacationCreateScriptJob *VacationPageWidget::writeScript() { if (mPageScript == Script) { KSieveUi::VacationCreateScriptJob *createJob = new KSieveUi::VacationCreateScriptJob; createJob->setServerUrl(mUrl); createJob->setServerName(mServerName); - const QString script = VacationUtils::composeScript( mVacationEditWidget->messageText(), - mVacationEditWidget->subject(), - mVacationEditWidget->notificationInterval(), - mVacationEditWidget->mailAliases(), - mVacationEditWidget->sendForSpam(), - mVacationEditWidget->domainName(), - mVacationEditWidget->startDate(), - mVacationEditWidget->endDate() ); const bool active = mVacationEditWidget->activateVacation(); + VacationUtils::Vacation vacation; + vacation.valid = true; + vacation.active = active; + vacation.messageText = mVacationEditWidget->messageText(); + vacation.subject = mVacationEditWidget->subject(); + vacation.mailAction = mVacationEditWidget->mailAction(); + vacation.mailActionRecipient = mVacationEditWidget->mailActionRecipient(); + vacation.notificationInterval = mVacationEditWidget->notificationInterval(); + vacation.aliases = mVacationEditWidget->mailAliases(); + vacation.sendForSpam = mVacationEditWidget->sendForSpam(); + vacation.excludeDomain = mVacationEditWidget->domainName(); + vacation.startDate = mVacationEditWidget->startDate(); + vacation.startTime = mVacationEditWidget->startTime(); + vacation.endDate = mVacationEditWidget->endDate(); + vacation.endTime = mVacationEditWidget->endTime(); + const QString script = VacationUtils::composeScript(vacation); createJob->setStatus(active, mWasActive); //Q_EMIT scriptActive( active, mServerName); createJob->setScript(script); return createJob; } return 0; } void VacationPageWidget::setDefault() { if (mVacationEditWidget->isEnabled()) mVacationEditWidget->setDefault(); } diff --git a/libksieve/ksieveui/vacation/vacationpagewidget.h b/libksieve/ksieveui/vacation/vacationpagewidget.h index 2e112353e1..887dc11d80 100644 --- a/libksieve/ksieveui/vacation/vacationpagewidget.h +++ b/libksieve/ksieveui/vacation/vacationpagewidget.h @@ -1,64 +1,70 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #ifndef VACATIONPAGEWIDGET_H #define VACATIONPAGEWIDGET_H #include #include class QStackedWidget; namespace KManageSieve { class SieveJob; } namespace KSieveUi { class VacationEditWidget; class VacationWarningWidget; class VacationCreateScriptJob; +class MultiImapVacationManager; +class ParseUserScriptJob; class VacationPageWidget : public QWidget { Q_OBJECT public: explicit VacationPageWidget(QWidget *parent=0); ~VacationPageWidget(); void setServerUrl(const KUrl &url); void setServerName(const QString &serverName); KSieveUi::VacationCreateScriptJob *writeScript(); void setDefault(); + void setVacationManager(MultiImapVacationManager *vacationManager); private slots: - void slotGetResult(KManageSieve::SieveJob *job, bool success, const QString &script, bool active); + void slotGetResult(const QString &serverName, const QStringList &sieveCapabilities, const QString &scriptName, const QString &script, bool active); private: + + void fillWithDefaults(); + enum PageType { Script = 0, ScriptNotSupported = 1 }; PageType mPageScript; QString mServerName; KUrl mUrl; QStackedWidget *mStackWidget; VacationEditWidget *mVacationEditWidget; VacationWarningWidget *mVacationWarningWidget; - KManageSieve::SieveJob *mSieveJob; + MultiImapVacationManager *mVacationManager; bool mWasActive; }; } #endif // VACATIONPAGEWIDGET_H diff --git a/libksieve/ksieveui/vacation/vacationscriptextractor.cpp b/libksieve/ksieveui/vacation/vacationscriptextractor.cpp index 42b5c6d53a..50a833bec4 100644 --- a/libksieve/ksieveui/vacation/vacationscriptextractor.cpp +++ b/libksieve/ksieveui/vacation/vacationscriptextractor.cpp @@ -1,125 +1,244 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #include "vacationscriptextractor.h" using namespace KSieveUi; VacationDataExtractor::VacationDataExtractor() : KSieve::ScriptBuilder(), mContext( None ), mNotificationInterval( 0 ) + , mActive(true) + , mInIfBlock(false) + , mFoundInBlock(false) + , mBlockLevel(0) + , mLineStart(0) + , mLineEnd(0) + , mMailAction(VacationUtils::Keep) + , mMailActionContext(None) { - kDebug(); } VacationDataExtractor::~VacationDataExtractor() { } -void VacationDataExtractor::commandStart( const QString & identifier ) { - kDebug() << "( \"" << identifier <<"\" )"; +void VacationDataExtractor::commandStart( const QString & identifier, int lineNumber ) { + if (identifier == QLatin1String("if") && mContext == None) { + mContext = IfBlock; + mLineStart = lineNumber; + mInIfBlock = true; + } + + if (commandFound() && (!mFoundInBlock || mBlockLevel > 0 )) { + if (identifier == QLatin1String("discard")) { + mMailAction = VacationUtils::Discard; + } else if (identifier == QLatin1String("redirect")) { + mMailAction = VacationUtils::Sendto; + mMailActionContext = RedirectCommand; + } + } + if ( identifier != QLatin1String("vacation") ) return; + + if (mContext != IfBlock) { + mLineStart = lineNumber; + } + reset(); mContext = VacationCommand; + mFoundInBlock = (mBlockLevel > 0); } -void VacationDataExtractor::commandEnd() { - kDebug(); - mContext = None; +void VacationDataExtractor::commandEnd(int lineNumber) { + if ( mContext != None && mContext != IfBlock && mContext != VacationEnd) { + mContext = VacationEnd; + mLineEnd = lineNumber; + } + mMailActionContext = None; } void VacationDataExtractor::error( const KSieve::Error & e ) { kDebug() << e.asString() << "@" << e.line() << "," << e.column(); } void VacationDataExtractor::finished() { } +void VacationDataExtractor::testStart(const QString &test) +{ + if (mContext == IfBlock) { + if (test == QLatin1String("true") || test == QLatin1String("false")) { + mActive = (test == QLatin1String("true")); + mIfComment = QString(); + } + } +} + +void VacationDataExtractor::hashComment(const QString &comment) +{ + if (mContext == IfBlock) { + mIfComment += comment; + } +} + + +void VacationDataExtractor::blockStart(int lineNumber) +{ + mBlockLevel++; +} + +void VacationDataExtractor::blockEnd(int lineNumber) +{ + mBlockLevel--; + if(mBlockLevel == 0 && !commandFound()) { //We are in main level again, and didn't found vacation in block + mActive = true; + mIfComment = QString(); + } else if (mInIfBlock && mBlockLevel == 0 && commandFound()) { + mLineEnd = lineNumber; + mInIfBlock = false; + } +} + void VacationDataExtractor::taggedArgument( const QString & tag ) { - kDebug() << "( \"" << tag <<"\" )"; + if (mMailActionContext == RedirectCommand) { + if (tag == QLatin1String("copy")) { + mMailAction = VacationUtils::CopyTo; + } + } if ( mContext != VacationCommand ) return; if ( tag == QLatin1String("days") ) mContext = Days; else if ( tag == QLatin1String("addresses") ) mContext = Addresses; else if (tag == QLatin1String("subject")) { mContext = Subject; } } void VacationDataExtractor::stringArgument( const QString & string, bool, const QString & ) { - kDebug() << "( \"" << string <<"\" )"; + if (mMailActionContext == RedirectCommand) { + mMailActionRecipient = string; + } if ( mContext == Addresses ) { mAliases.push_back( string ); mContext = VacationCommand; } else if (mContext == Subject) { mSubject = string; mContext = VacationCommand; } else if ( mContext == VacationCommand ) { mMessageText = string; mContext = VacationCommand; } } void VacationDataExtractor::numberArgument( unsigned long number, char ) { - kDebug() << "( \"" << number <<"\" )"; if ( mContext != Days ) return; if ( number > INT_MAX ) mNotificationInterval = INT_MAX; else mNotificationInterval = number; mContext = VacationCommand; } void VacationDataExtractor::stringListArgumentStart() { } void VacationDataExtractor::stringListEntry( const QString & string, bool, const QString & ) { - kDebug() << "( \"" << string <<"\" )"; if ( mContext != Addresses ) return; mAliases.push_back( string ); } void VacationDataExtractor::stringListArgumentEnd() { - kDebug(); if ( mContext != Addresses ) return; mContext = VacationCommand; } void VacationDataExtractor::reset() { - kDebug(); mContext = None; + mMailAction = VacationUtils::Keep; + mMailActionRecipient = QString(); mNotificationInterval = 0; mAliases.clear(); mMessageText.clear(); } + +RequireExtractor::RequireExtractor() + : KSieve::ScriptBuilder() + , mContext( None ) + , mLineStart(0) + , mLineEnd(0) +{ + +} + +RequireExtractor::~RequireExtractor() +{ + +} + +void RequireExtractor::commandStart(const QString &identifier, int lineNumber) +{ + if (identifier == QLatin1String("require") && mContext == None) { + mContext = RequireCommand; + mLineStart = lineNumber; + } +} + +void RequireExtractor::commandEnd(int lineNumber) +{ + if (mContext == RequireCommand) { + mContext = EndState; + mLineEnd = lineNumber; + } +} + +void RequireExtractor::error(const KSieve::Error &e) +{ + kDebug() << e.asString() << "@" << e.line() << "," << e.column(); +} + +void RequireExtractor::finished() +{ + +} + +void RequireExtractor::stringArgument(const QString &string, bool, const QString &) +{ + mRequirements << string; +} + +void RequireExtractor::stringListEntry(const QString &string, bool, const QString &) +{ + mRequirements << string; +} \ No newline at end of file diff --git a/libksieve/ksieveui/vacation/vacationscriptextractor.h b/libksieve/ksieveui/vacation/vacationscriptextractor.h index fc03e058a1..632f51dfbd 100644 --- a/libksieve/ksieveui/vacation/vacationscriptextractor.h +++ b/libksieve/ksieveui/vacation/vacationscriptextractor.h @@ -1,445 +1,560 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #ifndef VACATIONSCRIPTEXTRACTOR_H #define VACATIONSCRIPTEXTRACTOR_H #include "sieve-vacation.h" #include "util/util.h" +#include "vacationutils.h" #include #include #include #include #include #include #include #include #include namespace KSieveExt { class MultiScriptBuilder : public KSieve::ScriptBuilder { std::vector mBuilders; public: MultiScriptBuilder() : KSieve::ScriptBuilder() {} MultiScriptBuilder( KSieve::ScriptBuilder * sb1 ) : KSieve::ScriptBuilder(), mBuilders( 1 ) { mBuilders[0] = sb1; assert( sb1 ); } MultiScriptBuilder( KSieve::ScriptBuilder * sb1, KSieve::ScriptBuilder * sb2 ) : KSieve::ScriptBuilder(), mBuilders( 2 ) { mBuilders[0] = sb1; mBuilders[1] = sb2; assert( sb1 ); assert( sb2 ); } MultiScriptBuilder( KSieve::ScriptBuilder * sb1, KSieve::ScriptBuilder * sb2, KSieve::ScriptBuilder * sb3 ) : KSieve::ScriptBuilder(), mBuilders( 3 ) { mBuilders[0] = sb1; mBuilders[1] = sb2; mBuilders[2] = sb3; assert( sb1 ); assert( sb2 ); assert( sb3 ); } MultiScriptBuilder( KSieve::ScriptBuilder * sb1, KSieve::ScriptBuilder * sb2, KSieve::ScriptBuilder * sb3, KSieve::ScriptBuilder * sb4 ) : KSieve::ScriptBuilder(), mBuilders( 4 ) { mBuilders[0] = sb1; mBuilders[1] = sb2; mBuilders[2] = sb3; mBuilders[3] = sb4; assert( sb1 ); assert( sb2 ); assert( sb3 ); assert( sb4 ); } ~MultiScriptBuilder() {} private: #ifdef FOREACH #undef FOREACH #endif #define FOREACH for ( std::vector::const_iterator it = mBuilders.begin(), end = mBuilders.end() ; it != end ; ++it ) (*it)-> - void commandStart( const QString & identifier ) { FOREACH commandStart( identifier ); } - void commandEnd() { FOREACH commandEnd(); } + void commandStart( const QString & identifier, int lineNumber ) { FOREACH commandStart( identifier, lineNumber ); } + void commandEnd(int lineNumber) { FOREACH commandEnd(lineNumber); } void testStart( const QString & identifier ) { FOREACH testStart( identifier ); } void testEnd() { FOREACH testEnd(); } void testListStart() { FOREACH testListStart(); } void testListEnd() { FOREACH testListEnd(); } - void blockStart() { FOREACH blockStart(); } - void blockEnd() { FOREACH blockEnd(); } + void blockStart(int lineNumber) { FOREACH blockStart(lineNumber); } + void blockEnd(int lineNumber) { FOREACH blockEnd(lineNumber); } void hashComment( const QString & comment ) { FOREACH hashComment( comment ); } void bracketComment( const QString & comment ) { FOREACH bracketComment( comment ); } void lineFeed() { FOREACH lineFeed(); } void error( const KSieve::Error & e ) { FOREACH error( e ); } void finished() { FOREACH finished(); } void taggedArgument( const QString & tag ) { FOREACH taggedArgument( tag ); } void stringArgument( const QString & string, bool multiline, const QString & fixme ) { FOREACH stringArgument( string, multiline, fixme ); } void numberArgument( unsigned long number, char quantifier ) { FOREACH numberArgument( number, quantifier ); } void stringListArgumentStart() { FOREACH stringListArgumentStart(); } void stringListEntry( const QString & string, bool multiline, const QString & fixme) { FOREACH stringListEntry( string, multiline, fixme ); } void stringListArgumentEnd() { FOREACH stringListArgumentEnd(); } #undef FOREACH }; } namespace KSieveUi { class GenericInformationExtractor : public KSieve::ScriptBuilder { public: enum BuilderMethod { Any, TaggedArgument, StringArgument, NumberArgument, CommandStart, CommandEnd, TestStart, TestEnd, TestListStart, TestListEnd, BlockStart, BlockEnd, StringListArgumentStart, StringListEntry, StringListArgumentEnd }; struct StateNode { // expectation: int depth; BuilderMethod method; const char * string; // actions: int if_found; int if_not_found; const char * save_tag; }; const std::vector mNodes; std::map mResults; std::set mRecursionGuard; unsigned int mState; int mNestingDepth; + int mLineNumber; + public: GenericInformationExtractor( const std::vector & nodes ) - : KSieve::ScriptBuilder(), mNodes( nodes ), mState( 0 ), mNestingDepth( 0 ) {} + : KSieve::ScriptBuilder(), mNodes( nodes ), mState( 0 ), mNestingDepth( 0 ), mLineNumber(0) {} const std::map & results() const { return mResults; } private: virtual void process( BuilderMethod method, const QString & string=QString() ) { doProcess( method, string ); mRecursionGuard.clear(); } void doProcess( BuilderMethod method, const QString & string ) { mRecursionGuard.insert( mState ); bool found = true; const StateNode & expected = mNodes[mState]; if ( expected.depth != -1 && mNestingDepth != expected.depth ) found = false; if ( expected.method != Any && method != expected.method ) found = false; if ( const char * str = expected.string ) if ( string.toLower() != QString::fromUtf8( str ).toLower() ) found = false; - kDebug() << ( found ?"found:" :"not found:" ) + /*kDebug() << ( found ?"found:" :"not found:" ) << mState << "->" - << ( found ? expected.if_found : expected.if_not_found ); + << ( found ? expected.if_found : expected.if_not_found );*/ mState = found ? expected.if_found : expected.if_not_found ; assert( mState < mNodes.size() ); if ( found ) if ( const char * save_tag = expected.save_tag ) { - kDebug() << "stored tag" << save_tag << ":" << string; mResults[QString::fromLatin1(save_tag)] = string; } if ( !found && !mRecursionGuard.count( mState ) ) { doProcess( method, string ); } } - void commandStart( const QString & identifier ) { kDebug() ; process( CommandStart, identifier ); } - void commandEnd() { kDebug() ; process( CommandEnd ); } - void testStart( const QString & identifier ) { kDebug() ; process( TestStart, identifier ); } - void testEnd() { kDebug() ; process( TestEnd ); } - void testListStart() { kDebug() ; process( TestListStart ); } - void testListEnd() { kDebug() ; process( TestListEnd ); } - void blockStart() { kDebug() ; process( BlockStart ); ++mNestingDepth; } - void blockEnd() { kDebug() ; --mNestingDepth; process( BlockEnd ); } - void hashComment( const QString & ) { kDebug() ; } - void bracketComment( const QString & ) { kDebug() ; } - void lineFeed() { kDebug() ; } + void commandStart( const QString & identifier, int lineNumber ) { process( CommandStart, identifier ); } + void commandEnd(int lineNumber) { process( CommandEnd ); } + void testStart( const QString & identifier ) { process( TestStart, identifier ); } + void testEnd() { process( TestEnd ); } + void testListStart() { process( TestListStart ); } + void testListEnd() { process( TestListEnd ); } + void blockStart(int lineNumber) { process( BlockStart ); ++mNestingDepth; } + void blockEnd(int lineNumber) { --mNestingDepth; process( BlockEnd ); } + void hashComment( const QString & ) { } + void bracketComment( const QString & ) { } + void lineFeed() { ++mLineNumber; } void error( const KSieve::Error & ) { kDebug() ; mState = 0; } - void finished() { kDebug() ; } - - void taggedArgument( const QString & tag ) { kDebug() ; process( TaggedArgument, tag ); } - void stringArgument( const QString & string, bool, const QString & ) { kDebug() ; process( StringArgument, string ); } - void numberArgument( unsigned long number, char ) { kDebug(); process( NumberArgument, QString::number( number ) ); } - void stringListArgumentStart() { kDebug() ; process( StringListArgumentStart ); } - void stringListEntry( const QString & string, bool, const QString & ) { kDebug() ; process( StringListEntry, string ); } - void stringListArgumentEnd() { kDebug() ; process( StringListArgumentEnd ); } + void finished() { } + + void taggedArgument( const QString & tag ) { process( TaggedArgument, tag ); } + void stringArgument( const QString & string, bool, const QString & ) { process( StringArgument, string ); } + void numberArgument( unsigned long number, char ) { process( NumberArgument, QString::number( number ) ); } + void stringListArgumentStart() { process( StringListArgumentStart ); } + void stringListEntry( const QString & string, bool, const QString & ) { process( StringListEntry, string ); } + void stringListArgumentEnd() { process( StringListArgumentEnd ); } }; typedef GenericInformationExtractor GIE; static const GenericInformationExtractor::StateNode spamNodes[] = { { 0, GIE::CommandStart, "if", 1, 0, 0 }, // 0 - { 0, GIE::TestStart, "header", 2, 0, 0 }, // 1 - { 0, GIE::TaggedArgument, "contains", 3, 0, 0 }, // 2 + { 0, GIE::TestStart, "allof", 2, 3, 0 }, // 1 + { 0, GIE::TestListStart, 0, 3, 0, 0 }, // 2 + { 0, GIE::TestStart, "not", 4, 3, 0 }, // 3 + { 0, GIE::TestStart, "header", 5, 3, 0 }, // 4 + { 0, GIE::TaggedArgument, "contains", 6, 0, 0 }, // 5 // accept both string and string-list: - { 0, GIE::StringArgument, "x-spam-flag", 9, 4, "x-spam-flag" }, // 3 - { 0, GIE::StringListArgumentStart, 0, 5, 0, 0 }, // 4 - { 0, GIE::StringListEntry, "x-spam-flag", 6, 7, "x-spam-flag" }, // 5 - { 0, GIE::StringListEntry, 0, 6, 8, 0 }, // 6 - { 0, GIE::StringListArgumentEnd, 0, 0, 5, 0 }, // 7 - { 0, GIE::StringListArgumentEnd, 0, 9, 0, 0 }, // 8 + { 0, GIE::StringArgument, "x-spam-flag", 12, 7, "x-spam-flag" }, // 6 + { 0, GIE::StringListArgumentStart, 0, 8, 0, 0 }, // 7 + { 0, GIE::StringListEntry, "x-spam-flag", 9, 10, "x-spam-flag" }, // 8 + { 0, GIE::StringListEntry, 0, 9, 11, 0 }, // 9 + { 0, GIE::StringListArgumentEnd, 0, 0, 8, 0 }, // 10 + { 0, GIE::StringListArgumentEnd, 0, 12, 0, 0 }, // 11 // accept both string and string-list: - { 0, GIE::StringArgument, "yes", 15, 10, "spam-flag-yes" }, // 9 - { 0, GIE::StringListArgumentStart, 0, 11, 0, 0 }, // 10 - { 0, GIE::StringListEntry, "yes", 12, 13, "spam-flag-yes" }, // 11 - { 0, GIE::StringListEntry, 0, 12, 14, 0 }, // 12 - { 0, GIE::StringListArgumentEnd, 0, 0, 11, 0 }, // 13 - { 0, GIE::StringListArgumentEnd, 0, 15, 0, 0 }, // 14 + { 0, GIE::StringArgument, "yes", 18, 13, "spam-flag-yes" }, // 12 + { 0, GIE::StringListArgumentStart, 0, 14, 0, 0 }, // 13 + { 0, GIE::StringListEntry, "yes", 15, 16, "spam-flag-yes" }, // 14 + { 0, GIE::StringListEntry, 0, 15, 17, 0 }, // 15 + { 0, GIE::StringListArgumentEnd, 0, 0, 14, 0 }, // 16 + { 0, GIE::StringListArgumentEnd, 0, 18, 0, 0 }, // 17 - { 0, GIE::TestEnd, 0, 16, 0, 0 }, // 15 + { 0, GIE::TestEnd, 0, 21, 20, 0 }, // 18 + { 0, GIE::Any, 0, 21, 0, 0 }, // 19 + { 0, GIE::TestListEnd, 0, 21, 19, 0 }, // 20 // block of command, find "stop", take nested if's into account: - { 0, GIE::BlockStart, 0, 17, 0, 0 }, // 16 - { 1, GIE::CommandStart, "stop", 20, 19, "stop" }, // 17 - { -1, GIE::Any, 0, 17, 0, 0 }, // 18 - { 0, GIE::BlockEnd, 0, 0, 18, 0 }, // 19 + { 0, GIE::BlockStart, 0, 22, 18, 0 }, // 21 + { 1, GIE::CommandStart, "vacation", 24, 22, "vacation" }, // 22 + { 1, GIE::Any, 0, 24, 0, 0 }, // 23 + { 0, GIE::BlockEnd, 0, 25, 23, 0 }, // 24 - { -1, GIE::Any, 0, 20, 20, 0 }, // 20 end state + { -1, GIE::Any, 0, 25, 25, 0 }, // 25 end state }; static const unsigned int numSpamNodes = sizeof spamNodes / sizeof *spamNodes ; class SpamDataExtractor : public GenericInformationExtractor { public: SpamDataExtractor() : GenericInformationExtractor( std::vector( spamNodes, spamNodes + numSpamNodes ) ) { } bool found() const { return mResults.count( QLatin1String("x-spam-flag") ) && mResults.count( QLatin1String("spam-flag-yes") ) && - mResults.count( QLatin1String("stop") ) ; + mResults.count( QLatin1String("vacation") ) ; } }; // to understand this table, study the output of // libksieve/tests/parsertest // 'if not address :domain :contains ["from"] ["mydomain.org"] { keep; stop; }' static const GenericInformationExtractor::StateNode domainNodes[] = { { 0, GIE::CommandStart, "if", 1, 0, 0 }, // 0 - { 0, GIE::TestStart, "not", 2, 0, 0, }, // 1 - { 0, GIE::TestStart, "address", 3, 0, 0 }, // 2 + { 0, GIE::TestStart, "allof", 2, 3, 0 }, // 1 + { 0, GIE::TestListStart, 0, 3, 0, 0 }, // 2 + { 0, GIE::TestStart, "address", 4, 3, 0 }, // 3 // :domain and :contains in arbitrary order: - { 0, GIE::TaggedArgument, "domain", 4, 5, 0 }, // 3 - { 0, GIE::TaggedArgument, "contains", 7, 0, 0 }, // 4 - { 0, GIE::TaggedArgument, "contains", 6, 0, 0 }, // 5 - { 0, GIE::TaggedArgument, "domain", 7, 0, 0 }, // 6 + { 0, GIE::TaggedArgument, "domain", 5, 6, 0 }, // 4 + { 0, GIE::TaggedArgument, "contains", 8, 0, 0 }, // 5 + { 0, GIE::TaggedArgument, "contains", 7, 0, 0 }, // 6 + { 0, GIE::TaggedArgument, "domain", 8, 0, 0 }, // 7 // accept both string and string-list: - { 0, GIE::StringArgument, "from", 13, 8, "from" }, // 7 - { 0, GIE::StringListArgumentStart, 0, 9, 0, 0 }, // 8 - { 0, GIE::StringListEntry, "from", 10, 11, "from" }, // 9 - { 0, GIE::StringListEntry, 0, 10, 12, 0 }, // 10 - { 0, GIE::StringListArgumentEnd, 0, 0, 9, 0 }, // 11 - { 0, GIE::StringListArgumentEnd, 0, 13, 0, 0 }, // 12 + { 0, GIE::StringArgument, "from", 14, 9, "from" }, // 8 + { 0, GIE::StringListArgumentStart, 0, 10, 0, 0 }, // 9 + { 0, GIE::StringListEntry, "from", 11, 12, "from" }, // 10 + { 0, GIE::StringListEntry, 0, 11, 13, 0 }, // 11 + { 0, GIE::StringListArgumentEnd, 0, 0, 10, 0 }, // 12 + { 0, GIE::StringListArgumentEnd, 0, 14, 0, 0 }, // 13 // string: save, string-list: save last - { 0, GIE::StringArgument, 0, 17, 14, "domainName" }, // 13 - { 0, GIE::StringListArgumentStart, 0, 15, 0, 0 }, // 14 - { 0, GIE::StringListEntry, 0, 15, 16, "domainName" }, // 15 - { 0, GIE::StringListArgumentEnd, 0, 17, 0, 0 }, // 16 + { 0, GIE::StringArgument, 0, 18, 15, "domainName" }, // 14 + { 0, GIE::StringListArgumentStart, 0, 16, 0, 0 }, // 15 + { 0, GIE::StringListEntry, 0, 16, 17, "domainName" }, // 16 + { 0, GIE::StringListArgumentEnd, 0, 18, 0, 0 }, // 17 - { 0, GIE::TestEnd, 0, 18, 0, 0 }, // 17 - { 0, GIE::TestEnd, 0, 19, 0, 0 }, // 18 + { 0, GIE::TestEnd, 0, 18, 20, 0 }, // 18 + { 0, GIE::Any, 0, 18, 0, 0 }, // 19 // block of commands, find "stop", take nested if's into account: - { 0, GIE::BlockStart, 0, 20, 0, 0 }, // 19 - { 1, GIE::CommandStart, "stop", 23, 22, "stop" }, // 20 - { -1, GIE::Any, 0, 20, 0, 0 }, // 21 - { 0, GIE::BlockEnd, 0, 0, 21, 0 }, // 22 + { 0, GIE::BlockStart, 0, 21, 19, 0 }, // 20 + { 1, GIE::CommandStart, "vacation", 23, 21, "vacation" }, // 21 + { 1, GIE::Any, 0, 23, 0, 0 }, // 22 + { 0, GIE::BlockEnd, 0, 24, 22, 0 }, // 23 - { -1, GIE::Any, 0, 23, 23, 0 } // 23 end state + { -1, GIE::Any, 0, 24, 24, 0 } // 24 end state }; static const unsigned int numDomainNodes = sizeof domainNodes / sizeof *domainNodes ; class DomainRestrictionDataExtractor : public GenericInformationExtractor { public: DomainRestrictionDataExtractor() : GenericInformationExtractor( std::vector( domainNodes, domainNodes+numDomainNodes ) ) { } QString domainName() /*not const, since map::op[] isn't const*/ { - return mResults.count( QLatin1String("stop") ) && mResults.count( QLatin1String("from") ) + return mResults.count( QLatin1String("vacation") ) && mResults.count( QLatin1String("from") ) ? mResults[QLatin1String("domainName")] : QString(); } }; // if not allof (currentdate :value "ge" date "YYYY-MM-DD", // currentfate :value "le" date "YYYY-MM-DD) { keep; stop; } static const GenericInformationExtractor::StateNode datesNodes[] = { { 0, GIE::CommandStart, "if", 1, 0, 0 }, // 0 - { 0, GIE::TestStart, "not", 2, 0, 0 }, // 1 - { 0, GIE::TestStart, "allof", 3, 0, 0 }, // 2 + { 0, GIE::TestStart, "allof", 2, 0, 0 }, // 1 // handle startDate and endDate in arbitrary order - { 0, GIE::TestListStart, 0, 4, 0, 0 }, // 3 - { 0, GIE::TestStart, "currentdate", 5, 0, 0 }, // 4 - { 0, GIE::TaggedArgument, "value", 6, 0, 0 }, // 5 - { 0, GIE::StringArgument, "ge", 7, 9, 0 }, // 6 - { 0, GIE::StringArgument, "date", 8, 0, 0 }, // 7 - { 0, GIE::StringArgument, 0, 12, 0, "startDate" }, // 8 - { 0, GIE::StringArgument, "le", 10, 0, 0 }, // 9 - { 0, GIE::StringArgument, "date", 11, 0, 0 }, // 10 - { 0, GIE::StringArgument, 0, 12, 0, "endDate" }, // 11 - { 0, GIE::TestEnd, 0, 13, 0, 0 }, // 12 - - { 0, GIE::TestStart, "currentdate", 14, 0, 0 }, // 13 - { 0, GIE::TaggedArgument, "value", 15, 0, 0 }, // 14 - { 0, GIE::StringArgument, "le", 16, 18, 0 }, // 15 - { 0, GIE::StringArgument, "date", 17, 0, 0 }, // 16 - { 0, GIE::StringArgument, 0, 21, 0, "endDate" }, // 17 - { 0, GIE::StringArgument, "ge", 19, 0, 0 }, // 18 - { 0, GIE::StringArgument, "date", 20, 0, 0 }, // 19 - { 0, GIE::StringArgument, 0, 21, 0, "startDate" }, // 20 - { 0, GIE::TestEnd, 0, 22, 0, 0 }, // 21 - { 0, GIE::TestListEnd, 0, 23, 0, 0 }, // 22 - - { 0, GIE::TestEnd, 0, 24, 0, 0 }, // 23 - { 0, GIE::TestEnd, 0, 25, 0, 0 }, // 24 + { 0, GIE::TestListStart, 0, 3, 0, 0 }, // 2 + { 0, GIE::TestStart, "currentdate", 4, 3, 0 }, // 3 + { 0, GIE::TaggedArgument, "value", 5, 4, 0 }, // 4 + { 0, GIE::StringArgument, "ge", 6, 10, 0 }, // 5 + { 0, GIE::StringArgument, "date", 7, 8, 0 }, // 6 + { 0, GIE::StringArgument, 0, 15, 0, "startDate" }, // 7 + { 0, GIE::StringArgument, "iso8601", 9, 0, 0 }, // 8 + { 0, GIE::StringArgument, 0, 15, 0, "startDateTime" },// 9 + { 0, GIE::StringArgument, "le", 11, 0, 0 }, // 10 + { 0, GIE::StringArgument, "date", 12, 13, 0 }, // 11 + { 0, GIE::StringArgument, 0, 15, 0, "endDate" }, // 12 + { 0, GIE::StringArgument, "iso8601", 14, 0, 0 }, // 13 + { 0, GIE::StringArgument, 0, 15, 0, "endDateTime" }, // 14 + { 0, GIE::TestEnd, 0, 16, 0, 0 }, // 15 + + { 0, GIE::TestStart, "currentdate", 17, 16, 0 }, // 16 + { 0, GIE::TaggedArgument, "value", 18, 17, 0 }, // 17 + { 0, GIE::StringArgument, "le", 19, 23, 0 }, // 18 + { 0, GIE::StringArgument, "date", 20, 21, 0 }, // 19 + { 0, GIE::StringArgument, 0, 28, 0, "endDate" }, // 20 + { 0, GIE::StringArgument, "iso8601", 22, 0, 0 }, // 21 + { 0, GIE::StringArgument, 0, 28, 0, "endDateTime" }, // 22 + { 0, GIE::StringArgument, "ge", 24, 0, 0 }, // 23 + { 0, GIE::StringArgument, "date", 25, 26, 0 }, // 24 + { 0, GIE::StringArgument, 0, 28, 0, "startDate" }, // 25 + { 0, GIE::StringArgument, "iso8601", 27, 0, 0 }, // 26 + { 0, GIE::StringArgument, 0, 28, 0, "startDateTime" }, // 27 + { 0, GIE::TestEnd, 0, 32, 0, 0 }, // 28 + { 0, GIE::TestStart, 0, 31, 30, 0 }, // 29 + { -1, GIE::Any, 0, 32, 0, 0 }, // 30 + { 0, GIE::TestEnd, 0, 32, 30, 0 }, // 31 + { 0, GIE::TestListEnd, 0, 33, 29, 0 }, // 32 + + { 0, GIE::TestEnd, 0, 34, 0, 0 }, // 33 // block of commands, find "stop", take nested if's into account: - { 0, GIE::BlockStart, 0, 26, 0, 0 }, // 25 - { 1, GIE::CommandStart, "stop", 29, 28, "stop" }, // 26 - { -1, GIE::Any, 0, 26, 0, 0 }, // 27 - { 0, GIE::BlockEnd, 0, 0, 27, 0 }, // 28 + { 0, GIE::BlockStart, 0, 36, 33, 0 }, // 34 + { -1, GIE::Any, 0, 36, 0, 0 }, // 35 + { 1, GIE::CommandStart, "vacation", 38, 35, "vacation" }, // 36 + { -1, GIE::Any, 0, 38, 0, 0 }, // 37 + { 0, GIE::BlockEnd, 0, 39, 37, 0 }, // 38 - { -1, GIE::Any, 0, 27, 27, 0 } // 29 end state + { -1, GIE::Any, 0, 39, 39, 0 } // 39 end state }; static const unsigned int numDatesNodes = sizeof datesNodes / sizeof *datesNodes; class DateExtractor : public GenericInformationExtractor { public: DateExtractor() : GenericInformationExtractor( std::vector( datesNodes, datesNodes+numDatesNodes ) ) { } QDate endDate() { - return date( QLatin1String( "endDate" ) ); + if (mResults.count(QLatin1String("endDateTime")) == 1) { + return datetime(QLatin1String("endDateTime")).date(); + } else { + return date(QLatin1String("endDate")); + } } QDate startDate() { - return date( QLatin1String( "startDate" ) ); + if (mResults.count(QLatin1String("startDateTime")) == 1) { + return datetime(QLatin1String("startDateTime")).date(); + } else { + return date(QLatin1String("startDate")); + } + } + + QTime endTime() + { + return datetime(QLatin1String("endDateTime")).time(); + } + + QTime startTime() + { + return datetime(QLatin1String("startDateTime")).time(); } private: QDate date(const QString &name ) { if (mResults.count( name ) == 0) { return QDate(); } else { return QDate::fromString( mResults[name], Qt::ISODate ); } } + + QDateTime datetime(const QString &name ) { + if (mResults.count(name) == 0) { + return QDateTime(); + } else { + return QDateTime::fromString( mResults[name], Qt::ISODate ); + } + } }; class VacationDataExtractor : public KSieve::ScriptBuilder { enum Context { None = 0, // command itself: VacationCommand, // tagged args: - Days, Addresses, Subject + Days, Addresses, Subject, + VacationEnd, + IfBlock, + RedirectCommand }; public: VacationDataExtractor(); virtual ~VacationDataExtractor(); + bool commandFound() const { return mContext == VacationEnd; } + bool active() const { return mActive; } int notificationInterval() const { return mNotificationInterval; } const QString & messageText() const { return mMessageText; } const QStringList & aliases() const { return mAliases; } + const QString &ifComment() const { return mIfComment; } + VacationUtils::MailAction mailAction() const {return mMailAction;} + const QString &mailActionRecipient() const {return mMailActionRecipient;} const QString &subject() const { return mSubject; } + int lineStart() const {return mLineStart;} + int lineEnd() const {return mLineEnd;} + private: - void commandStart( const QString & identifier ); + void commandStart( const QString & identifier, int lineNumber ); - void commandEnd(); + void commandEnd(int lineNumber); - void testStart( const QString & ) {} + void testStart( const QString &); void testEnd() {} void testListStart() {} void testListEnd() {} - void blockStart() {} - void blockEnd() {} - void hashComment( const QString & ) {} - void bracketComment( const QString & ) {} + void blockStart(int lineNumber); + void blockEnd(int lineNumber); + void hashComment( const QString & ); + void bracketComment( const QString &c ) {} void lineFeed() {} void error( const KSieve::Error & e ); void finished(); void taggedArgument( const QString & tag ); void stringArgument( const QString & string, bool, const QString & ); void numberArgument( unsigned long number, char ); void stringListArgumentStart(); void stringListEntry( const QString & string, bool, const QString & ); void stringListArgumentEnd(); private: Context mContext; int mNotificationInterval; QString mMessageText; QString mSubject; QStringList mAliases; + bool mActive; + bool mInIfBlock; + bool mFoundInBlock; + int mBlockLevel; + QString mIfComment; + int mLineStart; + int mLineEnd; + + Context mMailActionContext; + VacationUtils::MailAction mMailAction; + QString mMailActionRecipient; void reset(); }; +class RequireExtractor : public KSieve::ScriptBuilder { + enum Context { + None = 0, + // command itself: + RequireCommand, + EndState + }; +public: + RequireExtractor(); + virtual ~RequireExtractor(); + + bool commandFound() const { return mContext == EndState; } + const QStringList &requirements() const { return mRequirements; } + + int lineStart() const {return mLineStart;} + int lineEnd() const {return mLineEnd;} + +private: + void commandStart( const QString & identifier, int lineNumber ); + + void commandEnd(int lineNumber); + + void testStart( const QString &) {} + void testEnd() {} + void testListStart() {} + void testListEnd() {} + void blockStart(int lineNumber){}; + void blockEnd(int lineNumber){}; + void hashComment( const QString & ) {} + void bracketComment( const QString & ) {} + void lineFeed() {} + void error( const KSieve::Error & e ); + void finished(); + + void taggedArgument( const QString & tag ) {} + void numberArgument( unsigned long number, char ) {} + + void stringArgument( const QString & string, bool, const QString & ); + + void stringListArgumentStart(){} + void stringListEntry( const QString & string, bool, const QString & ); + void stringListArgumentEnd(){} + +private: + Context mContext; + QStringList mRequirements; + int mLineStart; + int mLineEnd; +}; } #endif // VACATIONSCRIPTEXTRACTOR_H diff --git a/libksieve/ksieveui/vacation/vacationutils.cpp b/libksieve/ksieveui/vacation/vacationutils.cpp index 5428508675..44e067f0fd 100644 --- a/libksieve/ksieveui/vacation/vacationutils.cpp +++ b/libksieve/ksieveui/vacation/vacationutils.cpp @@ -1,201 +1,470 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #include "vacationutils.h" #include "vacationscriptextractor.h" #include "sieve-vacation.h" #include #include #include #include #include -#include +#include using KMime::Types::AddrSpecList; static inline QString dotstuff( QString s ) { // krazy:exclude=passbyvalue if ( s.startsWith( QLatin1Char('.') ) ) return QLatin1Char('.') + s.replace( QLatin1String("\n."), QLatin1String("\n..") ); else return s.replace( QLatin1String("\n."), QLatin1String("\n..") ); } static inline QString stringReplace(QString s) { s = s.replace(QRegExp(QLatin1String("[\n\t]+")),QLatin1String(" ")); return s.replace(QLatin1Char('\"'),QLatin1String("\\\"")); } +QString KSieveUi::VacationUtils::mailAction(KSieveUi::VacationUtils::MailAction action) +{ + switch (action) { + case Keep: + return i18n("Keep"); + case Discard: + return i18n("Discard"); + case Sendto: + return i18n("Redirect to"); + case CopyTo: + return i18n("Copy to"); + default: + kWarning() << "Unknown mail action" << action; + return i18n("Unkown action"); + } +} + +KSieveUi::VacationUtils::MailAction KSieveUi::VacationUtils::defaultMailAction() +{ + return KSieveUi::VacationUtils::Keep; +} + QString KSieveUi::VacationUtils::defaultSubject() { return i18n("Out of office till %1", QLocale().toString(QDate::currentDate().addDays(1))); } QString KSieveUi::VacationUtils::defaultMessageText() { return i18n( "I am out of office till %1.\n" "\n" "In urgent cases, please contact Mrs. \"vacation replacement\"\n" "\n" "email: \"email address of vacation replacement\"\n" "phone: +49 711 1111 11\n" "fax.: +49 711 1111 12\n" "\n" "Yours sincerely,\n" "-- \"enter your name and email address here\"\n", KGlobal::locale()->formatDate( QDate::currentDate().addDays( 1 ) ) ); } int KSieveUi::VacationUtils::defaultNotificationInterval() { return 7; // days } -QStringList KSieveUi::VacationUtils::defaultMailAliases() +KMime::Types::AddrSpecList KSieveUi::VacationUtils::defaultMailAliases() { - QStringList sl; + KMime::Types::AddrSpecList sl; KPIMIdentities::IdentityManager manager( true ); KPIMIdentities::IdentityManager::ConstIterator end(manager.end()); for ( KPIMIdentities::IdentityManager::ConstIterator it = manager.begin(); it != end ; ++it ) { if ( !(*it).primaryEmailAddress().isEmpty() ) { - sl.push_back( (*it).primaryEmailAddress() ); + KMime::Types::Mailbox a; + a.fromUnicodeString((*it).primaryEmailAddress()); + sl.push_back(a.addrSpec()); + } + foreach(const QString &email, (*it).emailAliases()) { + KMime::Types::Mailbox a; + a.fromUnicodeString(email); + sl.push_back(a.addrSpec()); } - sl += (*it).emailAliases(); } + return sl; } bool KSieveUi::VacationUtils::defaultSendForSpam() { return VacationSettings::outOfOfficeReactToSpam(); } QString KSieveUi::VacationUtils::defaultDomainName() { return VacationSettings::outOfOfficeDomain(); } QDate KSieveUi::VacationUtils::defaultStartDate() { return QDate::currentDate(); } QDate KSieveUi::VacationUtils::defaultEndDate() { return defaultStartDate().addDays(7); } -bool KSieveUi::VacationUtils::parseScript( const QString &script, QString &messageText, - QString &subject, - int & notificationInterval, QStringList &aliases, - bool & sendForSpam, QString &domainName, - QDate & startDate, QDate & endDate ) +KSieveUi::VacationUtils::Vacation KSieveUi::VacationUtils::parseScript(const QString &script) { + KSieveUi::VacationUtils::Vacation vacation; if ( script.trimmed().isEmpty() ) { - messageText = VacationUtils::defaultMessageText(); - subject = VacationUtils::defaultSubject(); - notificationInterval = VacationUtils::defaultNotificationInterval(); - aliases = VacationUtils::defaultMailAliases(); - sendForSpam = VacationUtils::defaultSendForSpam(); - domainName = VacationUtils::defaultDomainName(); - return true; + vacation.valid = false; + vacation.active = false; + vacation.mailAction = VacationUtils::defaultMailAction(); + vacation.messageText = VacationUtils::defaultMessageText(); + vacation.subject = VacationUtils::defaultSubject(); + vacation.notificationInterval = VacationUtils::defaultNotificationInterval(); + vacation.aliases = VacationUtils::defaultMailAliases(); + vacation.sendForSpam = VacationUtils::defaultSendForSpam(); + vacation.excludeDomain = VacationUtils::defaultDomainName(); + return vacation; } // The trimmed() call below prevents parsing errors. The // slave somehow omits the last \n, which results in a lone \r at // the end, leading to a parse error. const QByteArray scriptUTF8 = script.trimmed().toUtf8(); - kDebug() << "scriptUtf8 = \"" + scriptUTF8 +"\""; + //kDebug() << "scriptUtf8 = \"" + scriptUTF8 +"\""; KSieve::Parser parser( scriptUTF8.begin(), scriptUTF8.begin() + scriptUTF8.length() ); VacationDataExtractor vdx; SpamDataExtractor sdx; DomainRestrictionDataExtractor drdx; DateExtractor dx; - KSieveExt::MultiScriptBuilder tsb( &vdx, &sdx, &drdx, &dx ); + KSieveExt::MultiScriptBuilder tsb( &vdx , &sdx, &drdx, &dx ); parser.setScriptBuilder( &tsb ); - if ( !parser.parse() ) - return false; - messageText = vdx.messageText().trimmed(); + parser.parse(); + if ( !parser.parse() || !vdx.commandFound() ) { + vacation.active = false; + vacation.valid = false; + return vacation; + } + vacation.valid = true; + vacation.active = vdx.active(); + vacation.mailAction = vdx.mailAction(); + vacation.mailActionRecipient = vdx.mailActionRecipient(); + vacation.messageText = vdx.messageText().trimmed(); if (!vdx.subject().isEmpty()) { - subject = vdx.subject().trimmed(); + vacation.subject = vdx.subject().trimmed(); } - notificationInterval = vdx.notificationInterval(); - aliases = vdx.aliases(); - if ( !VacationSettings::allowOutOfOfficeUploadButNoSettings() ) { - sendForSpam = !sdx.found(); - domainName = drdx.domainName(); + vacation.notificationInterval = vdx.notificationInterval(); + vacation.aliases = KMime::Types::AddrSpecList(); + foreach(const QString &alias, vdx.aliases()) { + KMime::Types::Mailbox a; + a.fromUnicodeString(alias); + vacation.aliases.append(a.addrSpec()); + } + + if (!vacation.active && !vdx.ifComment().isEmpty()) { + const QByteArray newScript = QString::fromAscii("if ").toUtf8() + vdx.ifComment().toUtf8() + QString::fromLatin1("{vacation;}").toUtf8(); + tsb = KSieveExt::MultiScriptBuilder( &sdx, &drdx, &dx ); + KSieve::Parser parser( newScript.begin(), + newScript.begin() + newScript.length() ); + parser.setScriptBuilder( &tsb ); + if ( !parser.parse() ) { + vacation.valid = false; + return vacation; + } } - startDate = dx.startDate(); - endDate = dx.endDate(); - return true; + + vacation.sendForSpam = !sdx.found(); + vacation.excludeDomain = drdx.domainName(); + vacation.startDate = dx.startDate(); + vacation.startTime = dx.startTime(); + vacation.endDate = dx.endDate(); + vacation.endTime = dx.endTime(); + return vacation; } -QString KSieveUi::VacationUtils::composeScript( const QString & messageText, +QString composeOldScript( const QString & messageText, const QString &subject, int notificationInterval, const AddrSpecList & addrSpecs, bool sendForSpam, const QString & domain, const QDate & startDate, const QDate & endDate ) { QString addressesArgument; QStringList aliases; if ( !addrSpecs.empty() ) { addressesArgument += QLatin1String(":addresses [ "); QStringList sl; AddrSpecList::const_iterator end = addrSpecs.constEnd(); for ( AddrSpecList::const_iterator it = addrSpecs.begin() ; it != end; ++it ) { sl.push_back( QLatin1Char('"') + (*it).asString().replace( QLatin1Char('\\'), QLatin1String("\\\\") ).replace( QLatin1Char('"'), QLatin1String("\\\"") ) + QLatin1Char('"') ); aliases.push_back( (*it).asString() ); } addressesArgument += sl.join( QLatin1String(", ") ) + QLatin1String(" ] "); } QString script; if ( startDate.isValid() && endDate.isValid() ) { script = QString::fromLatin1("require [\"vacation\", \"relational\", \"date\"];\n\n" ); } else { script = QString::fromLatin1("require \"vacation\";\n\n" ); } if ( !sendForSpam ) script += QString::fromLatin1( "if header :contains \"X-Spam-Flag\" \"YES\"" " { keep; stop; }\n" ); // FIXME? if ( !domain.isEmpty() ) // FIXME script += QString::fromLatin1( "if not address :domain :contains \"from\" \"%1\" { keep; stop; }\n" ).arg( domain ); if ( startDate.isValid() && endDate.isValid() ) { script += QString::fromLatin1( "if not allof(currentdate :value \"ge\" \"date\" \"%1\"," " currentdate :value \"le\" \"date\" \"%2\")" " { keep; stop; }\n" ).arg( startDate.toString(Qt::ISODate), endDate.toString(Qt::ISODate) ); } script += QLatin1String("vacation "); script += addressesArgument; if ( notificationInterval > 0 ) script += QString::fromLatin1(":days %1 ").arg( notificationInterval ); if (!subject.trimmed().isEmpty()) { script += QString::fromLatin1(":subject \"%1\" ").arg(stringReplace(subject).trimmed()); } script += QString::fromLatin1("text:\n"); - script += dotstuff( messageText.isEmpty() ? VacationUtils::defaultMessageText() : messageText ); + script += dotstuff( messageText.isEmpty() ? KSieveUi::VacationUtils::defaultMessageText() : messageText ); script += QString::fromLatin1( "\n.\n;\n" ); return script; } +QString KSieveUi::VacationUtils::composeScript(const Vacation &vacation) +{ + QStringList condition; + QStringList require; + + require << QLatin1String("vacation"); + + if (vacation.startDate.isValid() || vacation.endDate.isValid()) { + require << QLatin1String("date"); + require << QLatin1String("relational"); + } + + if (vacation.startDate.isValid()) { + if (vacation.startTime.isValid()) { + KDateTime start(vacation.startDate, vacation.startTime); + condition.append(QString::fromLatin1("currentdate :value \"ge\" \"iso8601\" \"%1\"") + .arg(start.toString(KDateTime::ISODate))); + } else { + condition.append(QString::fromLatin1("currentdate :value \"ge\" \"date\" \"%1\"") + .arg(vacation.startDate.toString(Qt::ISODate))); + } + } + + if (vacation.endDate.isValid()) { + if (vacation.endTime.isValid()) { + KDateTime end(vacation.endDate, vacation.endTime); + condition.append(QString::fromLatin1("currentdate :value \"le\" \"iso8601\" \"%1\"") + .arg(end.toString(KDateTime::ISODate))); + } else { + condition.append(QString::fromLatin1("currentdate :value \"le\" \"date\" \"%1\"") + .arg(vacation.endDate.toString(Qt::ISODate))); + } + } + + if (!vacation.sendForSpam) { + condition.append(QString::fromLatin1("not header :contains \"X-Spam-Flag\" \"YES\"")); + } + + if (!vacation.excludeDomain.isEmpty()) { + condition.append(QString::fromLatin1("address :domain :contains \"from\" \"%1\"").arg( vacation.excludeDomain )); + } + + QString addressesArgument; + QStringList aliases; + if ( !vacation.aliases.empty() ) { + addressesArgument += QLatin1String(":addresses [ "); + QStringList sl; + AddrSpecList::const_iterator end = vacation.aliases.constEnd(); + for ( AddrSpecList::const_iterator it = vacation.aliases.begin() ; it != end; ++it ) { + sl.push_back( QLatin1Char('"') + (*it).asString().replace( QLatin1Char('\\'), QLatin1String("\\\\") ).replace( QLatin1Char('"'), QLatin1String("\\\"") ) + QLatin1Char('"') ); + aliases.push_back( (*it).asString() ); + } + addressesArgument += sl.join( QLatin1String(", ") ) + QLatin1String(" ] "); + } + + QString sVacation(QLatin1String("vacation ")); + sVacation += addressesArgument; + if ( vacation.notificationInterval > 0 ) + sVacation += QString::fromLatin1(":days %1 ").arg(vacation.notificationInterval); + + if (!vacation.subject.trimmed().isEmpty()) { + sVacation += QString::fromLatin1(":subject \"%1\" ").arg(stringReplace(vacation.subject).trimmed()); + } + + sVacation += QString::fromLatin1("text:\n"); + sVacation += dotstuff( vacation.messageText.isEmpty() ? VacationUtils::defaultMessageText() : vacation.messageText ); + sVacation += QString::fromLatin1( "\n.\n;" ); + + switch (vacation.mailAction) { + case VacationUtils::Keep: + break; + case VacationUtils::Discard: + sVacation += QLatin1String("\ndiscard;"); + break; + case VacationUtils::Sendto: + sVacation += QLatin1String("\nredirect \"") + vacation.mailActionRecipient + QLatin1String("\";"); + break; + case VacationUtils::CopyTo: + require << QLatin1String("copy"); + sVacation += QLatin1String("\nredirect :copy \"") + vacation.mailActionRecipient + QLatin1String("\";"); + break; + } + + QString script; + + script = QLatin1String("require [\"%1\"];\n\n"); + script = script.arg(require.join(QLatin1String("\", \""))); + + if (condition.count() == 0) { + if (vacation.active) { + script += sVacation; + } else { + script += QString::fromLatin1("if false\n{\n\t"); + script += sVacation; + script += QLatin1String("\n}"); + } + } else { + if (vacation.active) { + script += QString::fromLatin1("if allof(%1)\n{\n\t").arg(condition.join(QLatin1String(", "))); + } else { + script += QString::fromLatin1("if false # allof(%1)\n{\n\t").arg(condition.join(QLatin1String(", "))); + } + script += sVacation; + script += QLatin1String("\n}"); + } + + script += QLatin1String("\n"); + + return script; +} + + +QString KSieveUi::VacationUtils::mergeRequireLine(const QString &script, const QString scriptUpdate) +{ + const QByteArray scriptUTF8 = script.trimmed().toUtf8(); + const QByteArray scriptUpdateUTF8 = scriptUpdate.trimmed().toUtf8(); + + if (scriptUTF8.isEmpty()) { + return scriptUpdate; + } + + if (scriptUpdateUTF8.isEmpty()) { + return script; + } + + KSieve::Parser parser( scriptUTF8.begin(), + scriptUTF8.begin() + scriptUTF8.length() ); + KSieve::Parser parserUpdate( scriptUpdateUTF8.begin(), + scriptUpdateUTF8.begin() + scriptUpdateUTF8.length() ); + RequireExtractor rx, rxUpdate; + parser.setScriptBuilder(&rx); + parserUpdate.setScriptBuilder(&rxUpdate); + + int insert(0); + QStringList lines = script.split(QLatin1Char('\n')); + QSet requirements; + + if (parser.parse() && rx.commandFound()) { + insert = rx.lineStart(); + const int endOld(rx.lineEnd()); + for (int i=insert; i<=endOld; i++) { + lines.removeAt(insert); + } + requirements = rx.requirements().toSet(); + } + + if (parserUpdate.parse() && rxUpdate.commandFound()) { + requirements += rxUpdate.requirements().toSet(); + } + + if (requirements.count() > 1) { + QStringList req = requirements.toList(); + req.sort(); + lines.insert(insert, QString::fromLatin1("require [\"%1\"];").arg(req.join(QLatin1String("\", \"")))); + } else if (requirements.count() == 1) { + lines.insert(insert, QString::fromLatin1("require \"%1\";").arg(requirements.toList().first())); + } + + return lines.join(QLatin1String("\n")); +} + +QString KSieveUi::VacationUtils::updateVacationBlock(const QString &oldScript, const QString &newScript) +{ + const QByteArray oldScriptUTF8 = oldScript.trimmed().toUtf8(); + const QByteArray newScriptUTF8 = newScript.trimmed().toUtf8(); + + if (oldScriptUTF8.isEmpty()) { + return newScript; + } + + if (newScriptUTF8.isEmpty()) { + return oldScript; + } + + KSieve::Parser parserOld( oldScriptUTF8.begin(), + oldScriptUTF8.begin() + oldScriptUTF8.length() ); + KSieve::Parser parserNew( newScriptUTF8.begin(), + newScriptUTF8.begin() + newScriptUTF8.length() ); + VacationDataExtractor vdxOld, vdxNew; + RequireExtractor rx; + KSieveExt::MultiScriptBuilder tsb( &vdxOld , &rx ); + parserOld.setScriptBuilder(&tsb); + parserNew.setScriptBuilder(&vdxNew); + + int startOld(0); + + QStringList lines = oldScript.split(QLatin1Char('\n')); + + QString script; + if (parserOld.parse() && vdxOld.commandFound()) { + startOld = vdxOld.lineStart(); + const int endOld(vdxOld.lineEnd()); + for (int i=startOld; i<=endOld; i++) { + lines.removeAt(startOld); + } + } else { + if (rx.commandFound()) { // after require + startOld = rx.lineEnd() + 1; + } else { + startOld = 0; + } + } + + if (parserNew.parse() && vdxNew.commandFound()) { + const int startNew(vdxNew.lineStart()); + const int endNew(vdxNew.lineEnd()); + QStringList linesNew = newScript.split(QLatin1Char('\n')); + for(int i=endNew;i>=startNew;i--) { + lines.insert(startOld, linesNew.at(i)); + } + } + + return lines.join(QLatin1String("\n")); +} diff --git a/libksieve/ksieveui/vacation/vacationutils.h b/libksieve/ksieveui/vacation/vacationutils.h index 1e151ef22d..b492a60072 100644 --- a/libksieve/ksieveui/vacation/vacationutils.h +++ b/libksieve/ksieveui/vacation/vacationutils.h @@ -1,57 +1,78 @@ /* Copyright (c) 2013, 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #ifndef VACATIONUTILS_H #define VACATIONUTILS_H #include #include - -namespace KMime { -namespace Types { -struct AddrSpec; -typedef QList AddrSpecList; -} -} - -class QDate; +#include +#include namespace KSieveUi { namespace VacationUtils { + +enum MailAction { + Keep, + Discard, + Sendto, + CopyTo, +}; + QString defaultMessageText(); QString defaultSubject(); +MailAction defaultMailAction(); int defaultNotificationInterval(); -QStringList defaultMailAliases(); +KMime::Types::AddrSpecList defaultMailAliases(); bool defaultSendForSpam(); QString defaultDomainName(); QDate defaultStartDate(); QDate defaultEndDate(); -QString composeScript( const QString & messageText, const QString &subject, - int notificationInterval, - const KMime::Types::AddrSpecList & aliases, - bool sendForSpam, const QString & excludeDomain, - const QDate & startDate, const QDate & endDate ); -bool parseScript( const QString & script, QString & messageText, - QString &subject, - int & notificationInterval, QStringList & aliases, - bool & sendForSpam, QString & domainName, - QDate & startDate, QDate & endDate ); +struct Vacation { + Vacation():valid(false), active(false), notificationInterval(1), sendForSpam(true), mailAction(Keep) {} + bool isValid() const {return valid;} + + MailAction mailAction; + QString mailActionRecipient; + bool valid; + QString messageText; + QString subject; + bool active; + int notificationInterval; + KMime::Types::AddrSpecList aliases; + bool sendForSpam; + QString excludeDomain; + QDate startDate; + QTime startTime; + QDate endDate; + QTime endTime; +}; + +QString composeScript(const Vacation &vacation); + +KSieveUi::VacationUtils::Vacation parseScript(const QString &script); + +QString mergeRequireLine(const QString &script1, const QString script2); + +QString updateVacationBlock(const QString &oldScript, const QString &newScript); + +QString mailAction(MailAction action); } } #endif // VACATIONUTILS_H diff --git a/libksieve/ksieveui/widgets/managesievewidget.cpp b/libksieve/ksieveui/widgets/managesievewidget.cpp index b9e28d1427..a3c5e40838 100644 --- a/libksieve/ksieveui/widgets/managesievewidget.cpp +++ b/libksieve/ksieveui/widgets/managesievewidget.cpp @@ -1,428 +1,477 @@ /* Copyright (c) 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #include "managesievewidget.h" #include "managesievetreeview.h" #include "widgets/sievetreewidgetitem.h" #include +#include +#include +#include #include #include #include #include #include #include #include #include +#include using namespace KSieveUi; +Q_DECLARE_METATYPE(QTreeWidgetItem*) + ManageSieveWidget::ManageSieveWidget(QWidget *parent) : QWidget(parent), mClearAll( false ), mBlockSignal( false ) { QHBoxLayout *lay = new QHBoxLayout; lay->setMargin(0); mTreeView = new ManageSieveTreeView; #ifndef QT_NO_CONTEXTMENU connect( mTreeView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotContextMenuRequested(QPoint)) ); #endif connect( mTreeView, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(slotDoubleClicked(QTreeWidgetItem*)) ); connect( mTreeView, SIGNAL(itemSelectionChanged()), this, SLOT(slotUpdateButtons()) ); connect( mTreeView, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(slotItemChanged(QTreeWidgetItem*,int))); connect ( Solid::Networking::notifier(), SIGNAL(statusChanged(Solid::Networking::Status)), this, SLOT(slotSystemNetworkStatusChanged(Solid::Networking::Status)) ); lay->addWidget(mTreeView); setLayout(lay); QTimer::singleShot(0,this, SLOT(slotCheckNetworkStatus())); } ManageSieveWidget::~ManageSieveWidget() { clear(); } void ManageSieveWidget::slotCheckNetworkStatus() { slotSystemNetworkStatusChanged(Solid::Networking::status()); } void ManageSieveWidget::slotSystemNetworkStatusChanged(Solid::Networking::Status status) { if ( status == Solid::Networking::Connected || status == Solid::Networking::Unknown) { mTreeView->setEnabled(true); slotRefresh(); } else { mTreeView->setEnabled(false); mTreeView->setNetworkDown(false); } } ManageSieveTreeView *ManageSieveWidget::treeView() const { return mTreeView; } void ManageSieveWidget::killAllJobs() { mClearAll = true; QMap::const_iterator it = mJobs.constBegin(); while (it != mJobs.constEnd()) { it.key()->kill(); ++it; } mClearAll = false; mJobs.clear(); } bool ManageSieveWidget::serverHasError(QTreeWidgetItem *item) const { const QVariant variant = item->data( 0, SIEVE_SERVER_ERROR ); if ( variant.isValid() && variant.toBool()==true ) return true; return false; } void ManageSieveWidget::slotItemChanged(QTreeWidgetItem *item, int col) { if (!item || mBlockSignal || (col != 0) ) { return; } if ( !isFileNameItem( item ) ) return; QTreeWidgetItem *parent = item->parent(); if ( (mSelectedItems[parent] != item) && itemIsActived( item )) { mSelectedItems[parent] = item; changeActiveScript( parent, true ); } else { mSelectedItems[parent] = item; changeActiveScript( parent, false ); } } void ManageSieveWidget::slotContextMenuRequested( const QPoint& p ) { QTreeWidgetItem *item = mTreeView->itemAt( p ); if ( !item ) return; if ( !item->parent() && !mUrls.count( item )) return; QMenu menu; if ( isFileNameItem( item ) ) { // script items: menu.addAction( i18n( "Edit Script..." ), this, SLOT(slotEditScript()) ); menu.addAction( i18n( "Delete Script" ), this, SLOT(slotDeleteScript()) ); if ( itemIsActived( item ) ) { menu.addSeparator(); menu.addAction( i18n( "Deactivate Script" ), this, SLOT(slotDeactivateScript()) ); } } else if ( !item->parent() ) { // top-levels: if ( !serverHasError(item) && mJobs.keys(item).isEmpty()) menu.addAction( i18n( "New Script..." ), this, SLOT(slotNewScript()) ); } if ( !menu.actions().isEmpty() ) menu.exec( mTreeView->viewport()->mapToGlobal(p) ); } void ManageSieveWidget::slotNewScript() { QTreeWidgetItem *currentItem = mTreeView->currentItem(); if ( !currentItem ) return; if ( currentItem->parent() ) currentItem = currentItem->parent(); if ( !currentItem ) return; if ( !mUrls.count( currentItem ) ) return; KUrl u = mUrls[currentItem]; if ( u.isEmpty() ) return; bool ok = false; const QString name = KInputDialog::getText( i18n( "New Sieve Script" ), i18n( "Please enter a name for the new Sieve script:" ), i18n( "unnamed" ), &ok, this ); if ( !ok || name.isEmpty() ) return; - if (isProtectedName(name.toLower())) { + if (Util::isKep14ProtectedName(name)) { KMessageBox::error(this, i18n("You cannot use protected name."), i18n("New Script")); return; } u.setFileName( name ); QTreeWidgetItem * parentItem = currentItem; if (parentItem) { const int numberOfElement(parentItem->childCount()); for (int i = 0; i child(i)->text(0) == name) { KMessageBox::error( this, i18n( "Script name already used \"%1\".", name ), i18n( "New Script" ) ); return; } } } const QStringList currentCapabilities = currentItem->data(0, SIEVE_SERVER_CAPABILITIES).toStringList(); mBlockSignal = true; QTreeWidgetItem *newItem = new QTreeWidgetItem( currentItem ); newItem->setFlags(newItem->flags() & (Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::ItemIsSelectable)); newItem->setText(0,name); newItem->setCheckState(0,Qt::Unchecked); mBlockSignal = false; Q_EMIT newScript(u, currentCapabilities); } void ManageSieveWidget::slotEditScript() { QTreeWidgetItem *currentItem = mTreeView->currentItem(); if ( !isFileNameItem( currentItem ) ) return; QTreeWidgetItem* parent = currentItem->parent(); if ( !mUrls.count( parent ) ) return; KUrl url = mUrls[parent]; if ( url.isEmpty() ) return; url.setFileName( currentItem->text(0) ); const QStringList currentCapabilities = parent->data(0, SIEVE_SERVER_CAPABILITIES).toStringList(); Q_EMIT editScript(url, currentCapabilities); } void ManageSieveWidget::slotDeactivateScript() { QTreeWidgetItem * item = mTreeView->currentItem(); if ( !isFileNameItem( item ) ) return; QTreeWidgetItem *parent = item->parent(); if ( itemIsActived( item ) ) { mSelectedItems[parent] = item; changeActiveScript( parent, false ); } } void ManageSieveWidget::changeActiveScript( QTreeWidgetItem *item, bool activate ) { if ( !item ) return; if ( !mUrls.count( item ) ) return; if ( !mSelectedItems.count( item ) ) return; KUrl u = mUrls[item]; if ( u.isEmpty() ) return; + + if (item->data(0, SIEVE_SERVER_MODE).toInt() == Kep14EditorMode) { + QStringList activeScripts; + for(int i=0; i < item->childCount(); i++) { + QTreeWidgetItem *j = item->child(i); + if (itemIsActived(j)) { + activeScripts << j->text(0); + } + } + GenerateGlobalScriptJob *job = new GenerateGlobalScriptJob(u); + job->addUserActiveScripts(activeScripts); + connect( job, SIGNAL(success()), SLOT(slotRefresh())); + connect( job, SIGNAL(error(QString)), SLOT(slotRefresh())); + job->start(); + return; + } + QTreeWidgetItem* selected = mSelectedItems[item]; if ( !selected ) return; u.setFileName( selected->text(0) ); - KManageSieve::SieveJob * job; if ( activate ) job = KManageSieve::SieveJob::activate( u ); else job = KManageSieve::SieveJob::deactivate( u ); mBlockSignal = true; connect( job, SIGNAL(result(KManageSieve::SieveJob*,bool,QString,bool)), this, SLOT(slotRefresh()) ); } bool ManageSieveWidget::itemIsActived( QTreeWidgetItem *item ) const { Q_ASSERT( item && item->parent() ); return (item->checkState(0) == Qt::Checked); } bool ManageSieveWidget::isFileNameItem( QTreeWidgetItem *item ) const { if ( !item || !item->parent() ) return false; return (item->flags() & Qt::ItemIsEnabled); } void ManageSieveWidget::clear() { killAllJobs(); mSelectedItems.clear(); mUrls.clear(); mTreeView->clear(); } void ManageSieveWidget::slotDeleteScript() { QTreeWidgetItem * currentItem = mTreeView->currentItem(); if ( !isFileNameItem( currentItem ) ) return; QTreeWidgetItem *parent = currentItem->parent(); if ( !parent ) return; if ( !mUrls.count( parent ) ) return; KUrl u = mUrls[parent]; if ( u.isEmpty() ) return; u.setFileName( currentItem->text(0) ); if ( KMessageBox::warningContinueCancel( this, i18n( "Really delete script \"%1\" from the server?", u.fileName() ), i18n( "Delete Sieve Script Confirmation" ), KStandardGuiItem::del() ) != KMessageBox::Continue ) return; KManageSieve::SieveJob * job = KManageSieve::SieveJob::del( u ); connect( job, SIGNAL(result(KManageSieve::SieveJob*,bool,QString,bool)), this, SLOT(slotRefresh()) ); Q_EMIT scriptDeleted(u); } -bool ManageSieveWidget::isProtectedName(const QString &name) -{ - if (name == QLatin1String("master") || - name == QLatin1String("user") || - name == QLatin1String("management")) { - return true; - } - return false; -} - void ManageSieveWidget::slotRefresh() { mBlockSignal = true; clear(); const bool noImapFound = refreshList(); slotUpdateButtons(); mTreeView->setNoImapFound(noImapFound); if (!noImapFound) mBlockSignal = false; } void ManageSieveWidget::slotUpdateButtons() { Q_EMIT updateButtons(mTreeView->currentItem()); } void ManageSieveWidget::slotGotList(KManageSieve::SieveJob *job, bool success, const QStringList &listScript, const QString &activeScript) { qDebug()<<"void ManageSieveWidget::slotGotList(KManageSieve::SieveJob *job, bool success, const QStringList &listScript, const QString &activeScript) success: "<(parent))->stopAnimation(); mJobs.remove( job ); if (!success) { mBlockSignal = false; parent->setData( 0, SIEVE_SERVER_ERROR, true ); QTreeWidgetItem * item = new QTreeWidgetItem( parent ); item->setText( 0, i18n( "Failed to fetch the list of scripts" ) ); item->setFlags( item->flags() & ~Qt::ItemIsEnabled ); mTreeView->expandItem( parent ); return; } mBlockSignal = true; // don't trigger slotItemChanged Q_FOREACH (const QString &script, listScript) { //Hide protected name. - const QString lowerScript(script.toLower()); - if (isProtectedName(lowerScript)) + if (Util::isKep14ProtectedName(script)) continue; QTreeWidgetItem* item = new QTreeWidgetItem( parent ); item->setFlags(item->flags() & (Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::ItemIsSelectable)); item->setText(0, script); const bool isActive = (script == activeScript); item->setCheckState(0, isActive ? Qt::Checked : Qt::Unchecked); if ( isActive ) { mSelectedItems[parent] = item; } } mBlockSignal = false; - qDebug()<<" LOAD"; - const bool hasIncludeCapability = job->sieveCapabilities().contains(QLatin1String("include")); - const bool hasUserActiveScript = (activeScript.toLower() == QLatin1String("USER")); - QStringList mUserActiveScriptList; - if (hasUserActiveScript && hasIncludeCapability) { - //TODO parse file. + const bool hasKep14EditorMode = Util::hasKep14Support(job->sieveCapabilities(), listScript, activeScript); + if (hasKep14EditorMode) { + KUrl u = mUrls[parent]; + u.setFileName(QLatin1String("USER")); + ParseUserScriptJob *parseJob = new ParseUserScriptJob(u); + parseJob->setProperty(QLatin1String("parentItem").latin1(), QVariant::fromValue(parent)); + connect(parseJob, SIGNAL(finished(ParseUserScriptJob*)), SLOT(setActiveScripts(ParseUserScriptJob*))); + parseJob->start(); + (static_cast(parent))->startAnimation(); } parent->setData( 0, SIEVE_SERVER_CAPABILITIES, job->sieveCapabilities() ); parent->setData( 0, SIEVE_SERVER_ERROR, false ); - parent->setData( 0, SIEVE_SERVER_MODE, hasIncludeCapability ? Kep14EditorMode : NormalEditorMode); + parent->setData( 0, SIEVE_SERVER_MODE, hasKep14EditorMode ? Kep14EditorMode : NormalEditorMode); mTreeView->expandItem( parent ); } +void ManageSieveWidget::setActiveScripts(ParseUserScriptJob *job) +{ + QTreeWidgetItem * parent = job->property(QLatin1String("parentItem").latin1()).value(); + if ( !parent ) { + return; + } + (static_cast(parent))->stopAnimation(); + + if (!job->error().isEmpty()) { + qWarning() << job->error(); + return; + } + + mBlockSignal = true; // don't trigger slotItemChanged + const QStringList activeScriptList = job->activeScriptList(); + QStringList scriptOrder = activeScriptList; + QMap scriptMap; + + const int children = parent->childCount(); + for(int i=0; i < children; i++) { + QTreeWidgetItem *item = parent->takeChild(0); + scriptMap.insert(item->text(0), item); + const bool isActive = activeScriptList.contains(item->text(0)); + item->setCheckState(0, isActive ? Qt::Checked : Qt::Unchecked); + if (!isActive) { + scriptOrder << item->text(0); + } + } + + foreach(const QString &scriptName, scriptOrder) { + parent->addChild(scriptMap[scriptName]); + } + + mBlockSignal = false; +} + + void ManageSieveWidget::slotDoubleClicked( QTreeWidgetItem * item ) { if ( !isFileNameItem( item ) ) return; slotEditScript(); } void ManageSieveWidget::enableDisableActions(bool &newScriptAction, bool &editScriptAction, bool &deleteScriptAction, bool &desactivateScriptAction) { QTreeWidgetItem * item = mTreeView->currentItem(); bool enabled = true; if ( !item ) enabled = false; else if ( !item->parent() && !mUrls.count( item )) enabled = false; if ( !enabled ) { newScriptAction = false; editScriptAction = false; deleteScriptAction = false; desactivateScriptAction = false; } else { if ( serverHasError(item) || !mJobs.keys(item).isEmpty()) newScriptAction = false; else newScriptAction = mUrls.count( item ); enabled = isFileNameItem( item ); editScriptAction = enabled; deleteScriptAction = enabled; desactivateScriptAction = enabled && itemIsActived( item ); } } diff --git a/libksieve/ksieveui/widgets/managesievewidget.h b/libksieve/ksieveui/widgets/managesievewidget.h index 0e916e6793..dcf8f6c24a 100644 --- a/libksieve/ksieveui/widgets/managesievewidget.h +++ b/libksieve/ksieveui/widgets/managesievewidget.h @@ -1,105 +1,105 @@ /* Copyright (c) 2014 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #ifndef MANAGESIEVEWIDGET_H #define MANAGESIEVEWIDGET_H #include "ksieveui_export.h" #include #include #include #include class QTreeWidgetItem; namespace KManageSieve { class SieveJob; } namespace KSieveUi { class ManageSieveTreeView; +class ParseUserScriptJob; class KSIEVEUI_EXPORT ManageSieveWidget : public QWidget { Q_OBJECT public: enum SieveEditorMode { NormalEditorMode = 0, Kep14EditorMode }; explicit ManageSieveWidget(QWidget *parent=0); ~ManageSieveWidget(); ManageSieveTreeView *treeView() const; void enableDisableActions(bool &newScriptAction, bool &editScriptAction, bool &deleteScriptAction, bool &desactivateScriptAction); Q_SIGNALS: void updateButtons(QTreeWidgetItem *item); void newScript(const KUrl &u, const QStringList ¤tCapabilities); void editScript(const KUrl &url, const QStringList ¤tCapabilities); void scriptDeleted(const KUrl &u); protected: virtual bool refreshList() = 0; private Q_SLOTS: void slotItemChanged(QTreeWidgetItem *item, int col); void slotContextMenuRequested(const QPoint &p); void slotUpdateButtons(); void slotDoubleClicked(QTreeWidgetItem *item); void slotSystemNetworkStatusChanged(Solid::Networking::Status status); void slotCheckNetworkStatus(); + void setActiveScripts(ParseUserScriptJob *job); public Q_SLOTS: void slotGotList(KManageSieve::SieveJob *job, bool success, const QStringList &listScript, const QString &activeScript); void slotNewScript(); void slotEditScript(); void slotDeleteScript(); void slotDeactivateScript(); void slotRefresh(); protected: QMap mJobs; QMap mUrls; private: enum sieveServerStatus { SIEVE_SERVER_ERROR = Qt::UserRole +1, SIEVE_SERVER_CAPABILITIES = Qt::UserRole +2, SIEVE_SERVER_MODE = Qt::UserRole +3 }; bool serverHasError(QTreeWidgetItem *item) const; void killAllJobs(); void clear(); bool isFileNameItem(QTreeWidgetItem *item) const; bool itemIsActived(QTreeWidgetItem *item) const; void changeActiveScript(QTreeWidgetItem *item, bool activate); - bool isProtectedName(const QString &name); - // Maps top-level items to their child which has the radio button selection QMap mSelectedItems; ManageSieveTreeView *mTreeView; bool mClearAll : 1; bool mBlockSignal : 1; }; } #endif // MANAGESIEVEWIDGET_H diff --git a/libksieve/parser/lexer.cpp b/libksieve/parser/lexer.cpp index 1dfc848e7f..653c154e97 100644 --- a/libksieve/parser/lexer.cpp +++ b/libksieve/parser/lexer.cpp @@ -1,666 +1,666 @@ /* -*- c++ -*- parser/lexer.cpp This file is part of KSieve, the KDE internet mail/usenet news message filtering library. Copyright (c) 2002-2003 Marc Mutz KSieve is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. KSieve 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 In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include #include #include #include #include #include #include // std::auto_ptr #include #include // isdigit #ifdef STR_DIM # undef STR_DIM #endif #define STR_DIM(x) (sizeof(x) - 1) namespace KSieve { // // // Lexer Bridge implementation // // Lexer::Lexer( const char * scursor, const char * send, int options ) : i( 0 ) { i = new Impl( scursor, send, options ); } Lexer::~Lexer() { delete i; i = 0; } bool Lexer::ignoreComments() const { assert( i ); return i->ignoreComments(); } const Error & Lexer::error() const { assert( i ); return i->error(); } bool Lexer::atEnd() const { assert( i ); return i->atEnd(); } int Lexer::column() const { assert( i ); return i->column(); } int Lexer::line() const { assert( i ); return i->line(); } void Lexer::save() { assert( i ); i->save(); } void Lexer::restore() { assert( i ); i->restore(); } Lexer::Token Lexer::nextToken( QString & result ) { assert( i ); return i->nextToken( result ); } } // namespace KSieve // none except a-zA-Z0-9_ static const unsigned char iTextMap[16] = { 0x00, 0x00, 0x00, 0x00, // CTLs: none 0x00, 0x00, 0xFF, 0xC0, // SP ... '?': 0-9 0x7F, 0xFF, 0xFF, 0xE1, // '@' ... '_': A-Z_ 0x7F, 0xFF, 0xFF, 0xE0 // '`' ... DEL: a-z }; // SP, HT, CR, LF, {}[]();,#/ // ### exclude '['? Why would one want to write identifier["foo"]? static const unsigned char delimMap[16] = { 0x00, 0x64, 0x00, 0x00, // CTLs: CR, HT, LF 0x90, 0xC9, 0x00, 0x10, // SP ... '?': SP, #(),; 0x00, 0x00, 0x00, 0x16, // '@' ... '_': [] 0x00, 0x00, 0x00, 0x16 // '`' ... DEL: {} }; // All except iText, delim, "*: static const unsigned char illegalMap[16] = { 0xFF, 0x9B, 0xFF, 0xFF, 0x4F, 0x16, 0x00, 0x0F, 0x80, 0x00, 0x00, 0x0A, 0x80, 0x00, 0x00, 0x0A }; static inline bool isOfSet( const unsigned char map[16], unsigned char ch ) { assert( ch < 128 ); return ( map[ ch/8 ] & 0x80 >> ch%8 ); } static inline bool isIText( unsigned char ch ) { return ch <= 'z' && isOfSet( iTextMap, ch ); } static inline bool isDelim( unsigned char ch ) { return ch <= '}' && isOfSet( delimMap, ch ); } static inline bool isIllegal( unsigned char ch ) { return ch >= '~' || isOfSet( illegalMap, ch ); } #ifndef KDE_USE_FINAL static inline bool is8Bit( signed char ch ) { return ch < 0; } #endif static QString removeCRLF( const QString & s ) { const bool CRLF = s.endsWith( QLatin1String("\r\n") ); - const bool LF = !CRLF && s.endsWith( '\n' ); + const bool LF = !CRLF && s.endsWith( QLatin1Char('\n') ); const int e = CRLF ? 2 : LF ? 1 : 0 ; // what to chop off at the end return s.left( s.length() - e ); } static QString removeDotStuff( const QString & s ) { return s.startsWith( QLatin1String("..") ) ? s.mid( 1 ) : s ; } namespace KSieve { // // // Lexer Implementation // // Lexer::Impl::Impl( const char * scursor, const char * send, int options ) : mState( scursor ? scursor : send ), mEnd( send ? send : scursor ), mIgnoreComments( options & IgnoreComments ), mIgnoreLF( options & IgnoreLineFeeds ) { if ( !scursor || !send ) assert( atEnd() ); } Lexer::Token Lexer::Impl::nextToken( QString & result ) { assert( !atEnd() ); result.clear(); //clearErrors(); const int oldLine = line(); const bool eatingWSSucceeded = ignoreComments() ? eatCWS() : eatWS() ; if ( !ignoreLineFeeds() && oldLine != line() ) { result.setNum( line() - oldLine ); // return number of linefeeds encountered return LineFeeds; } if ( !eatingWSSucceeded ) return None; if ( atEnd() ) return None; switch ( *mState.cursor ) { case '#': // HashComment assert( !ignoreComments() ); ++mState.cursor; if ( !atEnd() ) parseHashComment( result, true ); return HashComment; case '/': // BracketComment assert( !ignoreComments() ); ++mState.cursor; // eat slash if ( atEnd() || *mState.cursor != '*' ) { makeError( Error::SlashWithoutAsterisk ); return BracketComment; } ++mState.cursor; // eat asterisk if ( atEnd() ) { makeError( Error::UnfinishedBracketComment ); return BracketComment; } parseBracketComment( result, true ); return BracketComment; case ':': // Tag ++mState.cursor; if ( atEnd() ) { makeError( Error::UnexpectedCharacter, line(), column() - 1 ); return Tag; } if ( !isIText( *mState.cursor ) ) { makeIllegalCharError( *mState.cursor ); return Tag; } parseTag( result ); return Tag; case '"': // QuotedString ++mState.cursor; parseQuotedString( result ); return QuotedString; case '{': case '}': case '[': case ']': case '(': case ')': case ';': case ',': // Special - result = *mState.cursor++; + result = QLatin1Char(*mState.cursor++); return Special; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // Number parseNumber( result ); return Number; case 't': // maybe MultiLineString, else Identifier if ( _strnicmp( mState.cursor, "text:", STR_DIM("text:") ) == 0 ) { // MultiLineString mState.cursor += STR_DIM("text:"); parseMultiLine( result ); // ### FIXME: There can be a hash-comment between "text:" // and CRLF! That should be preserved somehow... return MultiLineString; } // else fall through: default: // Identifier (first must not be 0-9, and can't (caught by Number above)) if ( !isIText( *mState.cursor ) ) { makeError( Error::IllegalCharacter ); return None; } parseIdentifier( result ); return Identifier; } } bool Lexer::Impl::eatWS() { while ( !atEnd() ) switch ( *mState.cursor ) { case '\r': case '\n': if ( !eatCRLF() ) return false; break; case ' ': case '\t': ++mState.cursor; break; default: return true; } // at end: return true; } bool Lexer::Impl::eatCRLF() { assert( !atEnd() ); assert( *mState.cursor == '\n' || *mState.cursor == '\r' ); if ( *mState.cursor == '\r' ) { ++mState.cursor; if ( atEnd() || *mState.cursor != '\n' ) { // CR w/o LF -> error makeError( Error::CRWithoutLF ); return false; } else { // good CRLF newLine(); return true; } } else /* *mState.cursor == '\n' */ { // good, LF only newLine(); return true; } } bool Lexer::Impl::parseHashComment( QString & result, bool reallySave ) { // hash-comment := "#" *CHAR-NOT-CRLF CRLF // check that the caller plays by the rules: assert( *(mState.cursor-1) == '#' ); const char * const commentStart = mState.cursor; // find next CRLF: while ( !atEnd() ) { if ( *mState.cursor == '\n' || *mState.cursor == '\r' ) break; ++mState.cursor; } const char * const commentEnd = mState.cursor - 1; if ( commentEnd == commentStart ) return true; // # was last char in script... if ( atEnd() || eatCRLF() ) { const int commentLength = commentEnd - commentStart + 1; if ( commentLength > 0 ) { if ( !isValidUtf8( commentStart, commentLength ) ) { makeError( Error::InvalidUTF8 ); return false; } if ( reallySave ) result += QString::fromUtf8( commentStart, commentLength ); } return true; } return false; } bool Lexer::Impl::parseBracketComment( QString & result, bool reallySave ) { // bracket-comment := "/*" *(CHAR-NOT-STAR / ("*" CHAR-NOT-SLASH )) "*/" // check that caller plays by the rules: assert( *(mState.cursor-2) == '/' ); assert( *(mState.cursor-1) == '*' ); const char * const commentStart = mState.cursor; const int commentCol = column() - 2; const int commentLine = line(); // find next asterisk: do { if ( !skipTo( '*' ) ) { if ( !error() ) makeError( Error::UnfinishedBracketComment, commentLine, commentCol ); return false; } } while ( !atEnd() && *++mState.cursor != '/' ); if ( atEnd() ) { makeError( Error::UnfinishedBracketComment, commentLine, commentCol ); return false; } assert( *mState.cursor == '/' ); const int commentLength = mState.cursor - commentStart - 1; if ( commentLength > 0 ) { if ( !isValidUtf8( commentStart, commentLength ) ) { makeError( Error::InvalidUTF8 ); return false; } if ( reallySave ) { QString tmp = QString::fromUtf8( commentStart, commentLength ); - result += tmp.remove( '\r' ); // get rid of CR in CRLF pairs + result += tmp.remove( QLatin1Char('\r') ); // get rid of CR in CRLF pairs } } ++mState.cursor; // eat '/' return true; } bool Lexer::Impl::parseComment( QString & result, bool reallySave ) { // comment := hash-comment / bracket-comment switch( *mState.cursor ) { case '#': ++mState.cursor; return parseHashComment( result, reallySave ); case '/': if ( charsLeft() < 2 || mState.cursor[1] != '*' ) { makeError( Error::IllegalCharacter ); return false; } else { mState.cursor += 2; // eat "/*" return parseBracketComment( result, reallySave ); } default: return false; // don't set an error here - there was no comment } } bool Lexer::Impl::eatCWS() { // white-space := 1*(SP / CRLF / HTAB / comment ) while ( !atEnd() ) { switch( *mState.cursor ) { case ' ': case '\t': // SP / HTAB ++mState.cursor; break;; case '\n': case '\r': // CRLF if ( !eatCRLF() ) return false; break; case '#': case '/': // comments { QString dummy; if ( !parseComment( dummy ) ) return false; } break; default: return true; } } return true; } bool Lexer::Impl::parseIdentifier( QString & result ) { // identifier := (ALPHA / "_") *(ALPHA DIGIT "_") assert( isIText( *mState.cursor ) ); const char * const identifierStart = mState.cursor; // first char: if ( isdigit( *mState.cursor ) ) { // no digits for the first makeError( Error::NoLeadingDigits ); return false; } // rest of identifier chars ( now digits are allowed ): for ( ++mState.cursor ; !atEnd() && isIText( *mState.cursor ) ; ++mState.cursor ) ; const int identifierLength = mState.cursor - identifierStart; // Can use the fast fromLatin1 here, since identifiers are always // in the us-ascii subset: result += QString::fromLatin1( identifierStart, identifierLength ); if ( atEnd() || isDelim( *mState.cursor ) ) return true; makeIllegalCharError( *mState.cursor ); return false; } bool Lexer::Impl::parseTag( QString & result ) { // tag := ":" identifier // check that the caller plays by the rules: assert( *(mState.cursor-1) == ':' ); assert( !atEnd() ); assert( isIText( *mState.cursor ) ); return parseIdentifier( result ); } bool Lexer::Impl::parseNumber( QString & result ) { // number := 1*DIGIT [QUANTIFIER] // QUANTIFIER := "K" / "M" / "G" assert( isdigit( *mState.cursor ) ); while ( !atEnd() && isdigit( *mState.cursor ) ) - result += *mState.cursor++; + result += QLatin1Char(*mState.cursor++); if ( atEnd() || isDelim( *mState.cursor ) ) return true; switch ( *mState.cursor ) { case 'G': case 'g': case 'M': case 'm': case 'K': case 'k': - result += *mState.cursor++; + result += QLatin1Char(*mState.cursor++); break; default: makeIllegalCharError(); return false; } // quantifier found. Check for delimiter: if ( atEnd() || isDelim( *mState.cursor ) ) return true; makeIllegalCharError(); return false; } bool Lexer::Impl::parseMultiLine( QString & result ) { // multi-line := "text:" *(SP / HTAB) (hash-comment / CRLF) // *(multi-line-literal / multi-line-dotstuff) // "." CRLF // multi-line-literal := [CHAR-NOT-DOT *CHAR-NOT-CRLF] CRLF // multi-line-dotstuff := "." 1*CHAR-NOT-CRLF CRLF // ;; A line containing only "." ends the multi-line. // ;; Remove a leading '.' if followed by another '.'. assert( _strnicmp( mState.cursor - 5, "text:", STR_DIM("text:") ) == 0 ); const int mlBeginLine = line(); const int mlBeginCol = column() - 5; while ( !atEnd() ) { switch ( *mState.cursor ) { case ' ': case '\t': ++mState.cursor; break; case '#': { ++mState.cursor; QString dummy; if ( !parseHashComment( dummy ) ) return false; goto MultiLineStart; // break from switch _and_ while } case '\n': case '\r': if ( !eatCRLF() ) return false; goto MultiLineStart; // break from switch _and_ while default: makeError( Error::NonCWSAfterTextColon ); return false; } } MultiLineStart: if ( atEnd() ) { makeError( Error::PrematureEndOfMultiLine, mlBeginLine, mlBeginCol ); return false; } // Now, collect the single lines until one with only a single dot is found: QStringList lines; while ( !atEnd() ) { const char * const oldBeginOfLine = beginOfLine(); if ( !skipToCRLF() ) return false; const int lineLength = mState.cursor - oldBeginOfLine; if ( lineLength > 0 ) { if ( !isValidUtf8( oldBeginOfLine, lineLength ) ) { makeError( Error::InvalidUTF8 ); return false; } const QString line = removeCRLF( QString::fromUtf8( oldBeginOfLine, lineLength ) ); lines.push_back( removeDotStuff( line ) ); - if ( line == "." ) + if ( line == QLatin1String(".") ) break; } else { lines.push_back( QString() ); } } - if ( lines.back() != "." ) { + if ( lines.back() != QLatin1String(".") ) { makeError( Error::PrematureEndOfMultiLine, mlBeginLine, mlBeginCol ); return false; } assert( !lines.empty() ); lines.erase( --lines.end() ); // don't include the lone dot. - result = lines.join("\n"); + result = lines.join(QLatin1String("\n")); return true; } bool Lexer::Impl::parseQuotedString( QString & result ) { // quoted-string := DQUOTE *CHAR DQUOTE // check that caller plays by the rules: assert( *(mState.cursor-1) == '"' ); const int qsBeginCol = column() - 1; const int qsBeginLine = line(); const QTextCodec * const codec = QTextCodec::codecForMib( 106 ); // UTF-8 assert( codec ); const std::auto_ptr dec( codec->makeDecoder() ); assert( dec.get() ); while ( !atEnd() ) switch ( *mState.cursor ) { case '"': ++mState.cursor; return true; case '\r': case '\n': if ( !eatCRLF() ) return false; - result += '\n'; + result += QLatin1Char('\n'); break; case '\\': ++mState.cursor; if ( atEnd() ) break; // else fall through: default: if ( !is8Bit( *mState.cursor ) ) - result += *mState.cursor++; + result += QLatin1Char(*mState.cursor++); else { // probably UTF-8 const char * const eightBitBegin = mState.cursor; skipTo8BitEnd(); const int eightBitLen = mState.cursor - eightBitBegin; assert( eightBitLen > 0 ); if ( isValidUtf8( eightBitBegin, eightBitLen ) ) result += dec->toUnicode( eightBitBegin, eightBitLen ); else { assert( column() >= eightBitLen ); makeError( Error::InvalidUTF8, line(), column() - eightBitLen ); return false; } } } makeError( Error::PrematureEndOfQuotedString, qsBeginLine, qsBeginCol ); return false; } void Lexer::Impl::makeIllegalCharError( char ch ) { makeError( isIllegal( ch ) ? Error::IllegalCharacter : Error::UnexpectedCharacter ); } } // namespace KSieve diff --git a/libksieve/parser/parser.cpp b/libksieve/parser/parser.cpp index 2192e68b90..df9ad7c7c2 100644 --- a/libksieve/parser/parser.cpp +++ b/libksieve/parser/parser.cpp @@ -1,650 +1,654 @@ /* -*- c++ -*- parser/parser.cpp This file is part of KSieve, the KDE internet mail/usenet news message filtering library. Copyright (c) 2002-2003 Marc Mutz KSieve is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. KSieve 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 In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include #include #include #include #include #include // ULONG_MAX #include // isdigit +#include + namespace KSieve { // // // Parser Bridge implementation // // Parser::Parser( const char * scursor, const char * const send, int options ) : i( 0 ) { i = new Impl( scursor, send, options ); } Parser::~Parser() { delete i; i = 0; } void Parser::setScriptBuilder( ScriptBuilder * builder ) { assert( i ); i->mBuilder = builder; } ScriptBuilder * Parser::scriptBuilder() const { assert( i ); return i->mBuilder; } const Error & Parser::error() const { assert( i ); return i->error(); } bool Parser::parse() { assert( i ); return i->parse(); } } static inline unsigned long factorForQuantifier( char ch ) { switch ( ch ) { case 'g': case 'G': return 1024*1024*1024; case 'm': case 'M': return 1024*1024; case 'k': case 'K': return 1024; default: assert( 0 ); // lexer should prohibit this return 1; // make compiler happy } } static inline bool willOverflowULong( unsigned long result, unsigned long add ) { static const unsigned long maxULongByTen = (unsigned long)(ULONG_MAX / 10.0) ; return result > maxULongByTen || ULONG_MAX - 10 * result < add ; } namespace KSieve { // // // Parser Implementation // // Parser::Impl::Impl( const char * scursor, const char * const send, int options ) : mToken( Lexer::None ), lexer( scursor, send, options ), mBuilder( 0 ) { } bool Parser::Impl::isStringToken() const { return token() == Lexer::QuotedString || token() == Lexer::MultiLineString ; } bool Parser::Impl::isArgumentToken() const { return isStringToken() || token() == Lexer::Number || token() == Lexer::Tag || - ( token() == Lexer::Special && mTokenValue == "[" ); + ( token() == Lexer::Special && mTokenValue == QLatin1String("[")) ; } bool Parser::Impl::obtainToken() { while ( !mToken && !lexer.atEnd() && !lexer.error() ) { mToken = lexer.nextToken( mTokenValue ); if ( lexer.error() ) break; // comments and line feeds are semantically invisible and may // appear anywhere, so we handle them here centrally: switch ( token() ) { case Lexer::HashComment: if ( scriptBuilder() ) scriptBuilder()->hashComment( tokenValue() ); consumeToken(); break; case Lexer::BracketComment: if ( scriptBuilder() ) scriptBuilder()->bracketComment( tokenValue() ); consumeToken(); break; case Lexer::LineFeeds: for ( unsigned int i = 0, end = tokenValue().toUInt() ; i < end ; ++i ) if ( scriptBuilder() ) // better check every iteration, b/c // we call out to ScriptBuilder, // where nasty things might happen! scriptBuilder()->lineFeed(); consumeToken(); break; default: ; // make compiler happy } } if ( lexer.error() && scriptBuilder() ) scriptBuilder()->error( lexer.error() ); return !lexer.error(); } bool Parser::Impl::parse() { // this is the entry point: START := command-list if ( !parseCommandList() ) return false; if ( !atEnd() ) { makeUnexpectedTokenError( Error::ExpectedCommand ); return false; } if ( scriptBuilder() ) scriptBuilder()->finished(); return true; } bool Parser::Impl::parseCommandList() { // our ABNF: // command-list := *comand while ( !atEnd() ) { if ( !obtainToken() ) return false; if ( token() == Lexer::None ) continue; if ( token() != Lexer::Identifier ) return true; if ( !parseCommand() ) { assert( error() ); return false; } } return true; } bool Parser::Impl::parseCommand() { // command := identifier arguments ( ";" / block ) // arguments := *argument [ test / test-list ] // block := "{" *command "}" // our ABNF: // block := "{" [ command-list ] "}" if ( atEnd() ) return false; // // identifier // if ( !obtainToken() || token() != Lexer::Identifier ) return false; - if ( scriptBuilder() ) - scriptBuilder()->commandStart( tokenValue() ); + if ( scriptBuilder() ) { + scriptBuilder()->commandStart( tokenValue(), lexer.line() ); + } consumeToken(); // // *argument // if ( !obtainToken() ) return false; if ( atEnd() ) { makeError( Error::MissingSemicolonOrBlock ); return false; } if ( isArgumentToken() && !parseArgumentList() ) { assert( error() ); return false; } // // test / test-list // if ( !obtainToken() ) return false; if ( atEnd() ) { makeError( Error::MissingSemicolonOrBlock ); return false; } - if ( token() == Lexer::Special && tokenValue() == "(" ) { // test-list + if ( token() == Lexer::Special && tokenValue() == QLatin1String ("(")) { // test-list if ( !parseTestList() ) { assert( error() ); return false; } } else if ( token() == Lexer::Identifier ) { // should be test: if ( !parseTest() ) { assert( error() ); return false; } } // // ";" / block // if ( !obtainToken() ) return false; if ( atEnd() ) { makeError( Error::MissingSemicolonOrBlock ); return false; } if ( token() != Lexer::Special ) { makeUnexpectedTokenError( Error::ExpectedBlockOrSemicolon ); return false; } - if ( tokenValue() == ";" ) + if ( tokenValue() == QLatin1String (";")) consumeToken(); - else if ( tokenValue() == "{" ) { // block + else if ( tokenValue() == QLatin1String ("{")) { // block if ( !parseBlock() ) return false; // it's an error since we saw '{' } else { makeError( Error::MissingSemicolonOrBlock ); return false; } - if ( scriptBuilder() ) - scriptBuilder()->commandEnd(); + if ( scriptBuilder() ) { + scriptBuilder()->commandEnd(lexer.line()); + } return true; } bool Parser::Impl::parseArgumentList() { // our ABNF: // argument-list := *argument while ( !atEnd() ) { if ( !obtainToken() ) return false; if ( !isArgumentToken() ) return true; if ( !parseArgument() ) return !error(); } return true; } bool Parser::Impl::parseArgument() { // argument := string-list / number / tag if ( !obtainToken() || atEnd() ) return false; if ( token() == Lexer::Number ) { if ( !parseNumber() ) { assert( error() ); return false; } return true; } else if ( token() == Lexer::Tag ) { if ( scriptBuilder() ) scriptBuilder()->taggedArgument( tokenValue() ); consumeToken(); return true; } else if ( isStringToken() ) { if ( scriptBuilder() ) scriptBuilder()->stringArgument( tokenValue(), token() == Lexer::MultiLineString, QString() ); consumeToken(); return true; - } else if ( token() == Lexer::Special && tokenValue() == "[" ) { + } else if ( token() == Lexer::Special && tokenValue() == QLatin1String("[")) { if ( !parseStringList() ) { assert( error() ); return false; } return true; } return false; } bool Parser::Impl::parseTestList() { // test-list := "(" test *("," test) ")" if ( !obtainToken() || atEnd() ) return false; - if ( token() != Lexer::Special || tokenValue() != "(" ) + if ( token() != Lexer::Special || tokenValue() != QLatin1String("(")) return false; if ( scriptBuilder() ) scriptBuilder()->testListStart(); consumeToken(); // generic while/switch construct for comma-separated lists. See // parseStringList() for another one. Any fix here is like to apply there, too. bool lastWasComma = true; while ( !atEnd() ) { if ( !obtainToken() ) return false; switch ( token() ) { case Lexer::None: break; case Lexer::Special: assert( tokenValue().length() == 1 ); assert( tokenValue()[0].toLatin1() ); switch ( tokenValue()[0].toLatin1() ) { case ')': consumeToken(); if ( lastWasComma ) { makeError( Error::ConsecutiveCommasInTestList ); return false; } if ( scriptBuilder() ) scriptBuilder()->testListEnd(); return true; case ',': consumeToken(); if ( lastWasComma ) { makeError( Error::ConsecutiveCommasInTestList ); return false; } lastWasComma = true; break; default: makeError( Error::NonStringInStringList ); return false; } break; case Lexer::Identifier: if ( !lastWasComma ) { makeError( Error::MissingCommaInTestList ); return false; } else { lastWasComma = false; if ( !parseTest() ) { assert( error() ); return false; } } break; default: makeUnexpectedTokenError( Error::NonTestInTestList ); return false; } } makeError( Error::PrematureEndOfTestList ); return false; } bool Parser::Impl::parseTest() { // test := identifier arguments // arguments := *argument [ test / test-list ] // // identifier // if ( !obtainToken() || atEnd() ) return false; if ( token() != Lexer::Identifier ) return false; if ( scriptBuilder() ) scriptBuilder()->testStart( tokenValue() ); consumeToken(); // // *argument // if ( !obtainToken() ) return false; if ( atEnd() ) // a test w/o args goto TestEnd; if ( isArgumentToken() && !parseArgumentList() ) { assert( error() ); return false; } // // test / test-list // if ( !obtainToken() ) return false; if ( atEnd() ) // a test w/o nested tests goto TestEnd; - if ( token() == Lexer::Special && tokenValue() == "(" ) { // test-list + if ( token() == Lexer::Special && tokenValue() == QLatin1String("(")) { // test-list if ( !parseTestList() ) { assert( error() ); return false; } } else if ( token() == Lexer::Identifier ) { // should be test: if ( !parseTest() ) { assert( error() ); return false; } } TestEnd: if ( scriptBuilder() ) scriptBuilder()->testEnd(); return true; } bool Parser::Impl::parseBlock() { // our ABNF: // block := "{" [ command-list ] "}" if ( !obtainToken() || atEnd() ) return false; - if ( token() != Lexer::Special || tokenValue() != "{" ) + if ( token() != Lexer::Special || tokenValue() != QLatin1String("{")) return false; if ( scriptBuilder() ) - scriptBuilder()->blockStart(); + scriptBuilder()->blockStart(lexer.line()); consumeToken(); if ( !obtainToken() ) return false; if ( atEnd() ) { makeError( Error::PrematureEndOfBlock ); return false; } if ( token() == Lexer::Identifier ) { if ( !parseCommandList() ) { assert( error() ); return false; } } if ( !obtainToken() ) return false; if ( atEnd() ) { makeError( Error::PrematureEndOfBlock ); return false; } - if ( token() != Lexer::Special || tokenValue() != "}" ) { + if ( token() != Lexer::Special || tokenValue() != QLatin1String("}")) { makeError( Error::NonCommandInCommandList ); return false; } if ( scriptBuilder() ) - scriptBuilder()->blockEnd(); + scriptBuilder()->blockEnd(lexer.line()); consumeToken(); return true; } bool Parser::Impl::parseStringList() { // string-list := "[" string *("," string) "]" / string // ;; if there is only a single string, the brackets are optional // // However, since strings are already handled separately from // string lists in parseArgument(), our ABNF is modified to: // string-list := "[" string *("," string) "]" if ( !obtainToken() || atEnd() ) return false; - if ( token() != Lexer::Special || tokenValue() != "[" ) + if ( token() != Lexer::Special || tokenValue() != QLatin1String("[") ) return false; if ( scriptBuilder() ) scriptBuilder()->stringListArgumentStart(); consumeToken(); // generic while/switch construct for comma-separated lists. See // parseTestList() for another one. Any fix here is like to apply there, too. bool lastWasComma = true; while ( !atEnd() ) { if ( !obtainToken() ) return false; switch ( token() ) { case Lexer::None: break; case Lexer::Special: assert( tokenValue().length() == 1 ); switch ( tokenValue()[0].toLatin1() ) { case ']': consumeToken(); if ( lastWasComma ) { makeError( Error::ConsecutiveCommasInStringList ); return false; } if ( scriptBuilder() ) scriptBuilder()->stringListArgumentEnd(); return true; case ',': consumeToken(); if ( lastWasComma ) { makeError( Error::ConsecutiveCommasInStringList ); return false; } lastWasComma = true; break; default: makeError( Error::NonStringInStringList ); return false; } break; case Lexer::QuotedString: case Lexer::MultiLineString: if ( !lastWasComma ) { makeError( Error::MissingCommaInStringList ); return false; } lastWasComma = false; if ( scriptBuilder() ) scriptBuilder()->stringListEntry( tokenValue(), token() == Lexer::MultiLineString, QString() ); consumeToken(); break; default: makeError( Error::NonStringInStringList ); return false; } } makeError( Error::PrematureEndOfStringList ); return false; } bool Parser::Impl::parseNumber() { // The lexer returns the number including the quantifier as a // single token value. Here, we split is an check that the number // is not out of range: if ( !obtainToken() || atEnd() ) return false; if ( token() != Lexer::Number ) return false; // number: unsigned long result = 0; int i = 0; const QByteArray s = tokenValue().toLatin1(); for ( const int len = s.length() ; i < len && isdigit( s[i] ) ; ++i ) { - const unsigned long digitValue = s[i] - '0' ; + const unsigned long digitValue = s[i] - QLatin1Char('0').toLatin1() ; if ( willOverflowULong( result, digitValue ) ) { makeError( Error::NumberOutOfRange ); return false; } else { result *= 10 ; result += digitValue ; } } // optional quantifier: char quantifier = '\0'; if ( i < s.length() ) { assert( i + 1 == s.length() ); quantifier = s[i]; const unsigned long factor = factorForQuantifier( quantifier ); if ( result > double(ULONG_MAX) / double(factor) ) { makeError( Error::NumberOutOfRange ); return false; } result *= factor; } if ( scriptBuilder() ) scriptBuilder()->numberArgument( result, quantifier ); consumeToken(); return true; } } // namespace KSieve diff --git a/libksieve/tests/parsertest.cpp b/libksieve/tests/parsertest.cpp index 57660c85b3..82eb8e1d9a 100644 --- a/libksieve/tests/parsertest.cpp +++ b/libksieve/tests/parsertest.cpp @@ -1,668 +1,668 @@ /* -*- c++ -*- tests/parsertest.cpp This file is part of the testsuite of KSieve, the KDE internet mail/usenet news message filtering library. Copyright (c) 2003 Marc Mutz KSieve is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. KSieve 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 In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include // SIZEOF_UNSIGNED_LONG #include using KSieve::Parser; #include #include #include #include #include using std::cout; using std::cerr; using std::endl; #include enum BuilderMethod { TaggedArgument, StringArgument, NumberArgument, CommandStart, CommandEnd, TestStart, TestEnd, TestListStart, TestListEnd, BlockStart, BlockEnd, StringListArgumentStart, StringListEntry, StringListArgumentEnd, HashComment, BracketComment, Error, Finished }; static const unsigned int MAX_RESPONSES = 100; struct TestCase { const char * name; const char * script; struct Response { BuilderMethod method; const char * string; bool boolean; } responses[MAX_RESPONSES]; } testCases[] = { // // single commands: // { "Null script", 0, { { Finished, 0, false } } }, { "Empty script", "", { { Finished, 0, false } } }, { "WS-only script", " \t\n\r\n", { { Finished, 0, false } } }, { "Bare hash comment", "#comment", { { HashComment, "comment", false }, { Finished, 0, false } } }, { "Bare bracket comment", "/*comment*/", { { BracketComment, "comment", false }, { Finished, 0, false } } }, { "Bare command", "command;", { { CommandStart, "command", false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "Bare command - missing semicolon", "command", { { CommandStart, "command", false }, { Error, "MissingSemicolonOrBlock", false } } }, { "surrounded by bracket comments", "/*comment*/command/*comment*/;/*comment*/", { { BracketComment, "comment", false }, { CommandStart, "command", false }, { BracketComment, "comment", false }, { CommandEnd, 0, false }, { BracketComment, "comment", false }, { Finished, 0, false } } }, { "surrounded by hash comments", "#comment\ncommand#comment\n;#comment", { { HashComment, "comment", false }, { CommandStart, "command", false }, { HashComment, "comment", false }, { CommandEnd, 0, false }, { HashComment, "comment", false }, { Finished, 0, false } } }, { "single tagged argument", "command :tag;", { { CommandStart, "command", false }, { TaggedArgument, "tag", false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single tagged argument - missing semicolon", "command :tag", { { CommandStart, "command", false }, { TaggedArgument, "tag", false }, { Error, "MissingSemicolonOrBlock", false } } }, { "single string argument - quoted string", "command \"string\";", { { CommandStart, "command", false }, { StringArgument, "string", false /*quoted*/ }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single string argument - multi-line string", "command text:\nstring\n.\n;", { { CommandStart, "command", false }, { StringArgument, "string", true /*multiline*/ }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single number argument - 100", "command 100;", { { CommandStart, "command", false }, { NumberArgument, "100 ", false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single number argument - 100k", "command 100k;", { { CommandStart, "command", false }, { NumberArgument, "102400k", false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single number argument - 100M", "command 100M;", { { CommandStart, "command", false }, { NumberArgument, "104857600M", false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single number argument - 2G", "command 2G;", { { CommandStart, "command", false }, { NumberArgument, "2147483648G", false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, #if SIZEOF_UNSIGNED_LONG == 8 # define ULONG_MAX_STRING "18446744073709551615" # define ULONG_MAXP1_STRING "18446744073709551616" #elif SIZEOF_UNSIGNED_LONG == 4 # define ULONG_MAX_STRING "4294967295" # define ULONG_MAXP1_STRING "4G" #else # error sizeof( unsigned long ) != 4 && sizeof( unsigned long ) != 8 ??? #endif { "single number argument - ULONG_MAX + 1", "command " ULONG_MAXP1_STRING ";", { { CommandStart, "command", false }, { Error, "NumberOutOfRange", false } } }, { "single number argument - ULONG_MAX", "command " ULONG_MAX_STRING ";", { { CommandStart, "command", false }, { NumberArgument, ULONG_MAX_STRING " ", false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single one-element string list argument - quoted string", "command [\"string\"];", { { CommandStart, "command", false }, { StringListArgumentStart, 0, false }, { StringListEntry, "string", false /*quoted*/ }, { StringListArgumentEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single one-element string list argument - multi-line string", "command [text:\nstring\n.\n];", { { CommandStart, "command", false }, { StringListArgumentStart, 0, false }, { StringListEntry, "string", true /*multiline*/ }, { StringListArgumentEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single two-element string list argument - quoted strings", "command [\"string\",\"string\"];", { { CommandStart, "command", false }, { StringListArgumentStart, 0, false }, { StringListEntry, "string", false /*quoted*/ }, { StringListEntry, "string", false /*quoted*/ }, { StringListArgumentEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single two-element string list argument - multi-line strings", "command [text:\nstring\n.\n,text:\nstring\n.\n];", { { CommandStart, "command", false }, { StringListArgumentStart, 0, false }, { StringListEntry, "string", true /*multiline*/ }, { StringListEntry, "string", true /*multiline*/ }, { StringListArgumentEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single two-element string list argument - quoted + multi-line strings", "command [\"string\",text:\nstring\n.\n];", { { CommandStart, "command", false }, { StringListArgumentStart, 0, false }, { StringListEntry, "string", false /*quoted*/ }, { StringListEntry, "string", true /*multiline*/ }, { StringListArgumentEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single two-element string list argument - multi-line + quoted strings", "command [text:\nstring\n.\n,\"string\"];", { { CommandStart, "command", false }, { StringListArgumentStart, 0, false }, { StringListEntry, "string", true /*multiline*/ }, { StringListEntry, "string", false /*quoted*/ }, { StringListArgumentEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "single bare test argument", "command test;", { { CommandStart, "command", false }, { TestStart, "test", false }, { TestEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "one-element test list argument", "command(test);", { { CommandStart, "command", false }, { TestListStart, 0, false }, { TestStart, "test", false }, { TestEnd, 0, false }, { TestListEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "two-element test list argument", "command(test,test);", { { CommandStart, "command", false }, { TestListStart, 0, false }, { TestStart, "test", false }, { TestEnd, 0, false }, { TestStart, "test", false }, { TestEnd, 0, false }, { TestListEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "zero-element block", "command{}", { { CommandStart, "command", false }, { BlockStart, 0, false }, { BlockEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "one-element block", "command{command;}", { { CommandStart, "command", false }, { BlockStart, 0, false }, { CommandStart, "command", false }, { CommandEnd, 0, false }, { BlockEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "two-element block", "command{command;command;}", { { CommandStart, "command", false }, { BlockStart, 0, false }, { CommandStart, "command", false }, { CommandEnd, 0, false }, { CommandStart, "command", false }, { CommandEnd, 0, false }, { BlockEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, { "command with a test with a test with a test", "command test test test;", { { CommandStart, "command", false }, { TestStart, "test", false }, { TestStart, "test", false }, { TestStart, "test", false }, { TestEnd, 0, false }, { TestEnd, 0, false }, { TestEnd, 0, false }, { CommandEnd, 0, false }, { Finished, 0, false } } }, }; static const int numTestCases = sizeof testCases / sizeof *testCases ; // Prints out the parse tree in XML-like format. For visual inspection // (manual tests). class PrintingScriptBuilder : public KSieve::ScriptBuilder { public: PrintingScriptBuilder() : KSieve::ScriptBuilder(), indent( 0 ) { write( "" ); } private: int indent; void write( const char * msg ) { for ( int i = 2*indent ; i > 0 ; --i ) cout << " "; cout << msg << endl; } void write( const QByteArray & key, const QString & value ) { if ( value.isEmpty() ) { write( QByteArray(QByteArray("<") + key + QByteArray("/>")) ); return; } write( QByteArray(QByteArray("<") + key + QByteArray(">")) ); ++indent; write( value.toUtf8().data() ); --indent; write( QByteArray(QByteArray("")) ); } }; // verifes that methods get called with expected arguments (and in // expected sequence) as specified by the TestCase. For automated // tests. class VerifyingScriptBuilder : public KSieve::ScriptBuilder { public: VerifyingScriptBuilder( const TestCase & testCase ) : KSieve::ScriptBuilder(), mNextResponse( 0 ), mTestCase( testCase ), mOk( true ) { } virtual ~VerifyingScriptBuilder() {} bool ok() const { return mOk; } void taggedArgument( const QString & tag ) { checkIs( TaggedArgument ); checkEquals( tag ); ++mNextResponse; } void stringArgument( const QString & string, bool multiline, const QString & /*fixme*/ ) { checkIs( StringArgument ); checkEquals( string ); checkEquals( multiline ); ++mNextResponse; } void numberArgument( unsigned long number, char quantifier ) { checkIs( NumberArgument ); checkEquals( QString::number( number ) + ( quantifier ? quantifier : ' ' ) ); ++mNextResponse; } - void commandStart( const QString & identifier ) { + void commandStart( const QString & identifier, int lineNumber ) { checkIs( CommandStart ); checkEquals( identifier ); ++mNextResponse; } - void commandEnd() { + void commandEnd(int lineNumber) { checkIs( CommandEnd ); ++mNextResponse; } void testStart( const QString & identifier ) { checkIs( TestStart ); checkEquals( identifier ); ++mNextResponse; } void testEnd() { checkIs( TestEnd ); ++mNextResponse; } void testListStart() { checkIs( TestListStart ); ++mNextResponse; } void testListEnd() { checkIs( TestListEnd ); ++mNextResponse; } - void blockStart() { + void blockStart(int lineNumber) { checkIs( BlockStart ); ++mNextResponse; } - void blockEnd() { + void blockEnd(int lineNumber) { checkIs( BlockEnd ); ++mNextResponse; } void stringListArgumentStart() { checkIs( StringListArgumentStart ); ++mNextResponse; } void stringListEntry( const QString & string, bool multiLine, const QString & /*fixme*/ ) { checkIs( StringListEntry ); checkEquals( string ); checkEquals( multiLine ); ++mNextResponse; } void stringListArgumentEnd() { checkIs( StringListArgumentEnd ); ++mNextResponse; } void hashComment( const QString & comment ) { checkIs( HashComment ); checkEquals( comment ); ++mNextResponse; } void bracketComment( const QString & comment ) { checkIs( BracketComment ); checkEquals( comment ); ++mNextResponse; } void lineFeed() { // FIXME } void error( const KSieve::Error & error ) { checkIs( Error ); checkEquals( QString( KSieve::Error::typeToString( error.type() ) ) ); ++mNextResponse; } void finished() { checkIs( Finished ); //++mNextResponse (no!) } private: const TestCase::Response & currentResponse() const { assert( mNextResponse <= MAX_RESPONSES ); return mTestCase.responses[mNextResponse]; } void checkIs( BuilderMethod m ) { if ( currentResponse().method != m ) { cerr << " expected method " << (int)currentResponse().method << ", got " << (int)m; mOk = false; } } void checkEquals( const QString & s ) { if ( s != QString::fromUtf8( currentResponse().string ) ) { cerr << " expected string arg \"" << ( currentResponse().string ? currentResponse().string : "" ) << "\", got \"" << ( s.isNull() ? "" : s.toUtf8().data() ) << "\""; mOk = false; } } void checkEquals( bool b ) { if ( b != currentResponse().boolean ) { cerr << " expected boolean arg <" << currentResponse().boolean << ">, got <" << b << ">"; mOk = false; } } unsigned int mNextResponse; const TestCase & mTestCase; bool mOk; }; int main( int argc, char * argv[] ) { if ( argc == 2 ) { // manual test const char * scursor = argv[1]; const char * const send = argv[1] + qstrlen( argv[1] ); Parser parser( scursor, send ); PrintingScriptBuilder psb; parser.setScriptBuilder( &psb ); if ( parser.parse() ) cout << "ok" << endl; else cout << "bad" << endl; } else if ( argc == 1 ) { // automated test bool success = true; for ( int i = 0 ; i < numTestCases ; ++i ) { const TestCase & t = testCases[i]; cerr << t.name << ":"; VerifyingScriptBuilder v( t ); Parser p( t.script, t.script + qstrlen( t.script ) ); p.setScriptBuilder( &v ); const bool ok = p.parse(); if ( v.ok() ) if ( ok ) cerr << " ok"; else cerr << " xfail"; else success = false; cerr << endl; } if ( !success ) exit( 1 ); } else { // usage error cerr << "usage: parsertest [ ]" << endl; exit( 1 ); } return 0; }