Klaas > Danimo, I am rather unpolite...
[owncloud:sebas-mirall.git] / src / mirall / application.cpp
1 /*
2  * Copyright (C) by Duncan Mac-Vicar P. <duncan@kde.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12  * for more details.
13  */
14
15 #include <iostream>
16
17 #include "mirall/application.h"
18 #include "mirall/folder.h"
19 #include "mirall/folderwatcher.h"
20 #include "mirall/folderwizard.h"
21 #include "mirall/networklocation.h"
22 #include "mirall/unisonfolder.h"
23 #include "mirall/owncloudfolder.h"
24 #include "mirall/statusdialog.h"
25 #include "mirall/owncloudsetupwizard.h"
26 #include "mirall/owncloudinfo.h"
27 #include "mirall/sslerrordialog.h"
28 #include "mirall/theme.h"
29 #include "mirall/mirallconfigfile.h"
30 #include "mirall/updatedetector.h"
31 #include "mirall/proxydialog.h"
32 #include "mirall/version.h"
33 #include "mirall/credentialstore.h"
34 #include "mirall/logger.h"
35
36 // for version information
37 #include "config.h"
38 #include <csync.h>
39
40 #ifdef WITH_CSYNC
41 #include "mirall/csyncfolder.h"
42 #endif
43 #include "mirall/inotify.h"
44
45 #include <QtCore>
46 #include <QtGui>
47 #include <QHash>
48 #include <QHashIterator>
49 #include <QUrl>
50 #include <QDesktopServices>
51 #include <QTranslator>
52 #include <QNetworkProxy>
53 #include <QNetworkProxyFactory>
54
55 namespace Mirall {
56
57 // application logging handler.
58 void mirallLogCatcher(QtMsgType type, const char *msg)
59 {
60   Q_UNUSED(type)
61   Logger::instance()->mirallLog( QString::fromUtf8(msg) );
62 }
63
64 // ----------------------------------------------------------------------------------
65
66 Application::Application(int &argc, char **argv) :
67     SharedTools::QtSingleApplication(argc, argv),
68     _tray(0),
69 #if QT_VERSION >= 0x040700
70     _networkMgr(new QNetworkConfigurationManager(this)),
71 #endif
72     _sslErrorDialog(0),
73     _contextMenu(0),
74     _theme(Theme::instance()),
75     _updateDetector(0),
76     _logBrowser(0),
77     _showLogWindow(false),
78     _logFlush(false),
79     _helpOnly(false)
80 {
81     setApplicationName( _theme->appName() );
82     setWindowIcon( _theme->applicationIcon() );
83
84     parseOptions(arguments());
85     setupLogBrowser();
86     //no need to waste time;
87     if ( _helpOnly ) return;
88     processEvents();
89
90     QTranslator *qtTranslator = new QTranslator(this);
91     qtTranslator->load(QLatin1String("qt_") + QLocale::system().name(),
92                       QLibraryInfo::location(QLibraryInfo::TranslationsPath));
93     installTranslator(qtTranslator);
94
95     QTranslator *mirallTranslator = new QTranslator(this);
96
97     QString locale = Theme::instance()->enforcedLocale();
98     if (locale.isEmpty()) locale = QLocale::system().name();
99
100 #ifdef Q_OS_LINUX
101     // FIXME - proper path!
102     mirallTranslator->load(QLatin1String("mirall_") + locale, QLatin1String("/usr/share/mirall/i18n/"));
103 #endif
104 #ifdef Q_OS_MAC
105     mirallTranslator->load(QLatin1String("mirall_") + locale, applicationDirPath()+QLatin1String("/../translations") ); // path defaults to app dir.
106 #endif
107 #ifdef Q_OS_WIN32
108     mirallTranslator->load(QLatin1String("mirall_") + locale, applicationDirPath());
109 #endif
110
111     installTranslator(mirallTranslator);
112
113     connect( this, SIGNAL(messageReceived(QString)), SLOT(slotParseOptions(QString)));
114     connect( Logger::instance(), SIGNAL(guiLog(QString,QString)),
115              this, SLOT(slotShowTrayMessage(QString,QString)));
116     // create folder manager for sync folder management
117     _folderMan = new FolderMan(this);
118     connect( _folderMan, SIGNAL(folderSyncStateChange(QString)),
119              this,SLOT(slotSyncStateChange(QString)));
120
121     /* use a signal mapper to map the open requests to the alias names */
122     _folderOpenActionMapper = new QSignalMapper(this);
123     connect(_folderOpenActionMapper, SIGNAL(mapped(const QString &)),
124             this, SLOT(slotFolderOpenAction(const QString &)));
125
126     setQuitOnLastWindowClosed(false);
127
128     _folderWizard = new FolderWizard;
129
130     _owncloudSetupWizard = new OwncloudSetupWizard( _folderMan, _theme, this );
131     connect( _owncloudSetupWizard, SIGNAL(ownCloudWizardDone(int)),
132              this, SLOT(slotownCloudWizardDone(int)));
133
134     _statusDialog = new StatusDialog( _theme );
135     connect( _statusDialog, SIGNAL(addASync()), this, SLOT(slotAddFolder()) );
136
137     connect( _statusDialog, SIGNAL(removeFolderAlias( const QString&)),
138              SLOT(slotRemoveFolder(const QString&)));
139
140     connect( _statusDialog, SIGNAL(openLogBrowser()), this, SLOT(slotOpenLogBrowser()));
141
142     connect( _statusDialog, SIGNAL(enableFolderAlias(QString,bool)),
143              SLOT(slotEnableFolder(QString,bool)));
144     connect( _statusDialog, SIGNAL(infoFolderAlias(const QString&)),
145              SLOT(slotInfoFolder( const QString&)));
146     connect( _statusDialog, SIGNAL(openFolderAlias(const QString&)),
147              SLOT(slotFolderOpenAction(QString)));
148
149 #if 0
150 #if QT_VERSION >= 0x040700
151     qDebug() << "* Network is" << (_networkMgr->isOnline() ? "online" : "offline");
152     foreach (const QNetworkConfiguration& netCfg, _networkMgr->allConfigurations(QNetworkConfiguration::Active)) {
153         //qDebug() << "Network:" << netCfg.identifier();
154     }
155 #endif
156 #endif
157     setupActions();
158     setupSystemTray();
159     setupProxy();
160     processEvents();
161
162     QObject::connect( this, SIGNAL(messageReceived(QString)),
163                          this, SLOT(slotOpenStatus()) );
164
165     QTimer::singleShot( 0, this, SLOT( slotStartFolderSetup() ));
166
167     MirallConfigFile cfg;
168     if( !cfg.ownCloudSkipUpdateCheck() ) {
169         QTimer::singleShot( 3000, this, SLOT( slotStartUpdateDetector() ));
170     }
171
172     connect( ownCloudInfo::instance(), SIGNAL(sslFailed(QNetworkReply*, QList<QSslError>)),
173              this,SLOT(slotSSLFailed(QNetworkReply*, QList<QSslError>)));
174
175     qDebug() << "Network Location: " << NetworkLocation::currentLocation().encoded();
176 }
177
178 Application::~Application()
179 {
180     delete _tray; // needed, see ctor
181     qDebug() << "* Mirall shutdown";
182 }
183
184 void Application::slotStartUpdateDetector()
185 {
186     _updateDetector = new UpdateDetector(this);
187     _updateDetector->versionCheck(_theme);
188
189 }
190
191 void Application::slotStartFolderSetup( int result )
192 {
193     if( result == QDialog::Accepted ) {
194         if( ownCloudInfo::instance()->isConfigured() ) {
195             connect( ownCloudInfo::instance(),SIGNAL(ownCloudInfoFound(QString,QString,QString,QString)),
196                      SLOT(slotOwnCloudFound(QString,QString,QString,QString)));
197
198             connect( ownCloudInfo::instance(),SIGNAL(noOwncloudFound(QNetworkReply*)),
199                      SLOT(slotNoOwnCloudFound(QNetworkReply*)));
200
201             ownCloudInfo::instance()->checkInstallation();
202         } else {
203             _owncloudSetupWizard->startWizard(true); // with intro
204         }
205     } else {
206         qDebug() << "Setup Wizard was canceled. No reparsing of config.";
207     }
208 }
209
210 void Application::slotOwnCloudFound( const QString& url, const QString& versionStr, const QString& version, const QString& edition)
211 {
212     qDebug() << "** Application: ownCloud found: " << url << " with version " << versionStr << "(" << version << ")";
213     // now check the authentication
214     MirallConfigFile cfgFile;
215     cfgFile.setOwnCloudVersion( version );
216     // disconnect from ownCloudInfo
217     disconnect( ownCloudInfo::instance(),SIGNAL(ownCloudInfoFound(QString,QString,QString,QString)),
218                 this, SLOT(slotOwnCloudFound(QString,QString,QString,QString)));
219
220     disconnect( ownCloudInfo::instance(),SIGNAL(noOwncloudFound(QNetworkReply*)),
221                 this, SLOT(slotNoOwnCloudFound(QNetworkReply*)));
222
223     if( version.startsWith("4.0") ) {
224         QMessageBox::warning(0, tr("%1 Server Mismatch").arg(_theme->appName()),
225                              tr("<p>The configured server for this client is too old.</p>"
226                                 "<p>Please update to the latest %1 server and restart the client.</p>").arg(_theme->appName()));
227         return;
228     }
229
230     QTimer::singleShot( 0, this, SLOT( slotFetchCredentials() ));
231 }
232
233 void Application::slotNoOwnCloudFound( QNetworkReply* reply )
234 {
235     qDebug() << "** Application: NO ownCloud found!";
236     QString msg;
237     if( reply ) {
238         QString url( reply->url().toString() );
239         url.remove( QLatin1String("/status.php") );
240         msg = tr("<p>The %1 at %2 could not be reached.</p>").arg(_theme->appName()).arg( url );
241         msg += tr("<p>The detailed error message is<br/><tt>%1</tt></p>").arg( reply->errorString() );
242     }
243     msg += tr("<p>Please check your configuration by clicking on the tray icon.</p>");
244
245     QMessageBox::warning(0, tr("%1 Connection Failed").arg(_theme->appName()), msg );
246     _actionAddFolder->setEnabled( false );
247
248     // Disconnect.
249     disconnect( ownCloudInfo::instance(),SIGNAL(ownCloudInfoFound(QString,QString,QString,QString)),
250                 this, SLOT(slotOwnCloudFound(QString,QString,QString,QString)));
251
252     disconnect( ownCloudInfo::instance(),SIGNAL(noOwncloudFound(QNetworkReply*)),
253                 this, SLOT(slotNoOwnCloudFound(QNetworkReply*)));
254
255     disconnect( ownCloudInfo::instance(),SIGNAL(ownCloudDirExists(QString,QNetworkReply*)),
256                 this,SLOT(slotAuthCheck(QString,QNetworkReply*)));
257
258     setupContextMenu();
259 }
260
261 void Application::slotFetchCredentials()
262 {
263     QString trayMessage;
264
265     if( CredentialStore::instance()->canTryAgain() ) {
266         connect( CredentialStore::instance(), SIGNAL(fetchCredentialsFinished(bool)),
267                  this, SLOT(slotCredentialsFetched(bool)) );
268         CredentialStore::instance()->fetchCredentials();
269         if( CredentialStore::instance()->state() == CredentialStore::TooManyAttempts ) {
270             trayMessage = tr("Too many attempts to get a valid password.");
271         }
272     } else {
273         qDebug() << "Can not try again to fetch Credentials.";
274          trayMessage = tr("ownCloud user credentials are wrong. Please check configuration.");
275     }
276
277     if( !trayMessage.isEmpty() ) {
278         _tray->showMessage(tr("Credentials"), trayMessage);
279         _actionOpenStatus->setEnabled( false );
280         _actionAddFolder->setEnabled( false );
281     }
282 }
283
284 void Application::slotCredentialsFetched(bool ok)
285 {
286     qDebug() << "Credentials successfully fetched: " << ok;
287     if( ! ok ) {
288         QString trayMessage;
289         trayMessage = tr("Error: Could not retrieve the password!");
290         if( CredentialStore::instance()->state() == CredentialStore::UserCanceled ) {
291             trayMessage = tr("Password dialog was canceled!");
292         } else {
293             trayMessage = CredentialStore::instance()->errorMessage();
294         }
295
296         if( !trayMessage.isEmpty() ) {
297             _tray->showMessage(tr("Credentials"), trayMessage);
298         }
299
300         qDebug() << "Could not fetch credentials";
301         _actionAddFolder->setEnabled( false );
302         _actionOpenStatus->setEnabled( false );
303     } else {
304         // Credential fetched ok.
305         QTimer::singleShot( 0, this, SLOT( slotCheckAuthentication() ));
306     }
307     disconnect( CredentialStore::instance(), SIGNAL(fetchCredentialsFinished(bool)) );
308 }
309
310 void Application::slotCheckAuthentication()
311 {
312     connect( ownCloudInfo::instance(),SIGNAL(ownCloudDirExists(QString,QNetworkReply*)),
313              this,SLOT(slotAuthCheck(QString,QNetworkReply*)));
314
315     qDebug() << "# checking for authentication settings.";
316     ownCloudInfo::instance()->getRequest(QLatin1String("/"), true ); // this call needs to be authenticated.
317     // simply GET the webdav root, will fail if credentials are wrong.
318     // continue in slotAuthCheck here :-)
319 }
320
321 void Application::slotAuthCheck( const QString& ,QNetworkReply *reply )
322 {
323     bool ok = true;
324
325     if( reply->error() == QNetworkReply::AuthenticationRequiredError ) { // returned if the user is wrong.
326         qDebug() << "******** Password is wrong!";
327         QMessageBox::warning(0, tr("No %1 Connection").arg(_theme->appName()),
328                              tr("<p>Your %1 credentials are not correct.</p>"
329                                 "<p>Please correct them by starting the configuration dialog from the tray!</p>")
330                              .arg(_theme->appName()));
331         _actionAddFolder->setEnabled( false );
332         ok = false;
333     } else if( reply->error() == QNetworkReply::OperationCanceledError ) {
334         // the username was wrong and ownCloudInfo was closing the request after a couple of auth tries.
335         qDebug() << "******** Username or password is wrong!";
336         QMessageBox::warning(0, tr("No %1 Connection").arg(_theme->appName()),
337                              tr("<p>Either your user name or your password are not correct.</p>"
338                                 "<p>Please correct it by starting the configuration dialog from the tray!</p>"));
339         _actionAddFolder->setEnabled( false );
340         ok = false;
341     }
342
343     // disconnect from ownCloud Info signals
344     disconnect( ownCloudInfo::instance(),SIGNAL(ownCloudDirExists(QString,QNetworkReply*)),
345              this,SLOT(slotAuthCheck(QString,QNetworkReply*)));
346
347     if( ok ) {
348         qDebug() << "######## Credentials are ok!";
349         int cnt = _folderMan->setupFolders();
350         if( cnt ) {
351             _tray->setIcon( _theme->syncStateIcon( SyncResult::NotYetStarted, true ) );
352             _tray->show();
353             processEvents();
354
355             if( _tray )
356                 _tray->showMessage(tr("%1 Sync Started").arg(_theme->appName()),
357                                    tr("Sync started for %1 configured sync folder(s).").arg(cnt));
358
359             _statusDialog->setFolderList( _folderMan->map() );
360             computeOverallSyncStatus();
361
362         }
363         _actionAddFolder->setEnabled( true );
364         _actionOpenStatus->setEnabled( true );
365         setupContextMenu();
366     } else {
367         slotFetchCredentials();
368     }
369 }
370
371 void Application::slotSSLFailed( QNetworkReply *reply, QList<QSslError> errors )
372 {
373     qDebug() << "SSL-Warnings happened for url " << reply->url().toString();
374
375     if( ownCloudInfo::instance()->certsUntrusted() ) {
376         // User decided once to untrust. Honor this decision.
377         qDebug() << "Untrusted by user decision, returning.";
378         return;
379     }
380
381     QString configHandle = ownCloudInfo::instance()->configHandle(reply);
382
383     // make the ssl dialog aware of the custom config. It loads known certs.
384     if( ! _sslErrorDialog ) {
385         _sslErrorDialog = new SslErrorDialog;
386     }
387     _sslErrorDialog->setCustomConfigHandle( configHandle );
388
389     if( _sslErrorDialog->setErrorList( errors ) ) {
390         // all ssl certs are known and accepted. We can ignore the problems right away.
391         qDebug() << "Certs are already known and trusted, Warnings are not valid.";
392         reply->ignoreSslErrors();
393     } else {
394         if( _sslErrorDialog->exec() == QDialog::Accepted ) {
395             if( _sslErrorDialog->trustConnection() ) {
396                 reply->ignoreSslErrors();
397             } else {
398                 // User does not want to trust.
399                 ownCloudInfo::instance()->setCertsUntrusted(true);
400             }
401         } else {
402             ownCloudInfo::instance()->setCertsUntrusted(true);
403         }
404     }
405 }
406
407 void Application::slotownCloudWizardDone( int res )
408 {
409     if( res == QDialog::Accepted ) {
410
411     }
412     slotStartFolderSetup( res );
413 }
414
415 void Application::setupActions()
416 {
417     _actionOpenoC = new QAction(tr("Open %1 in browser...").arg(_theme->appName()), this);
418     QObject::connect(_actionOpenoC, SIGNAL(triggered(bool)), SLOT(slotOpenOwnCloud()));
419     _actionOpenStatus = new QAction(tr("Open status..."), this);
420     QObject::connect(_actionOpenStatus, SIGNAL(triggered(bool)), SLOT(slotOpenStatus()));
421     _actionOpenStatus->setEnabled( false );
422     _actionAddFolder = new QAction(tr("Add folder..."), this);
423     QObject::connect(_actionAddFolder, SIGNAL(triggered(bool)), SLOT(slotAddFolder()));
424     _actionConfigure = new QAction(tr("Configure..."), this);
425     QObject::connect(_actionConfigure, SIGNAL(triggered(bool)), SLOT(slotConfigure()));
426     _actionConfigureProxy = new QAction(tr("Configure proxy..."), this);
427     QObject::connect(_actionConfigureProxy, SIGNAL(triggered(bool)), SLOT(slotConfigureProxy()));
428     _actionAbout = new QAction(tr("About..."), this);
429     QObject::connect(_actionAbout, SIGNAL(triggered(bool)), SLOT(slotAbout()));
430     _actionQuit = new QAction(tr("Quit"), this);
431     QObject::connect(_actionQuit, SIGNAL(triggered(bool)), SLOT(quit()));
432 }
433
434 void Application::setupSystemTray()
435 {
436     // Setting a parent heres will crash on X11 since by the time qapp runs
437     // its childrens dtors, the X11->screen variable queried for is gone -> crash
438     _tray = new QSystemTrayIcon;
439     _tray->setIcon( _theme->syncStateIcon( SyncResult::NotYetStarted, true ) );
440
441     connect(_tray,SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
442             SLOT(slotTrayClicked(QSystemTrayIcon::ActivationReason)));
443
444     setupContextMenu();
445
446     _tray->show();
447 }
448
449 void Application::setupContextMenu()
450 {
451     bool isConfigured = ownCloudInfo::instance()->isConfigured();
452
453     _actionOpenStatus->setEnabled(isConfigured);
454     _actionOpenoC->setEnabled(isConfigured);
455     _actionAddFolder->setEnabled(isConfigured);
456
457     if( _contextMenu ) {
458         _contextMenu->clear();
459     } else {
460         _contextMenu = new QMenu();
461         // this must be called only once after creating the context menu, or
462         // it will trigger a bug in Ubuntu's SNI bridge patch (11.10, 12.04).
463         _tray->setContextMenu(_contextMenu);
464     }
465     _contextMenu->setTitle(_theme->appName() );
466     _contextMenu->addAction(_actionOpenStatus);
467     _contextMenu->addAction(_actionOpenoC);
468
469     _contextMenu->addSeparator();
470
471     int folderCnt = _folderMan->map().size();
472     // add open actions for all sync folders to the tray menu
473     if( _theme->singleSyncFolder() ) {
474         if( folderCnt == 0 ) {
475             // if there is no folder configured yet, show the add action.
476             _contextMenu->addAction(_actionAddFolder);
477         } else {
478             // there should be exactly one folder. No sync-folder add action will be shown.
479             QStringList li = _folderMan->map().keys();
480             if( li.size() == 1 ) {
481                 Folder *folder = _folderMan->map().value(li.first());
482                 if( folder ) {
483                     // if there is singleFolder mode, a generic open action is displayed.
484                     QAction *action = new QAction( tr("Open %1 folder").arg(_theme->appName()), this);
485                     action->setIcon( _theme->trayFolderIcon( folder->backend()) );
486
487                     connect( action, SIGNAL(triggered()),_folderOpenActionMapper,SLOT(map()));
488                     _folderOpenActionMapper->setMapping( action, folder->alias() );
489
490                     _contextMenu->addAction(action);
491                 }
492             }
493         }
494     } else {
495         // show a grouping with more than one folder.
496         if ( folderCnt ) {
497             _contextMenu->addAction(tr("Managed Folders:"))->setDisabled(true);
498         }
499         foreach (Folder *folder, _folderMan->map() ) {
500             QAction *action = new QAction( folder->alias(), this );
501             action->setIcon( _theme->trayFolderIcon( folder->backend()) );
502
503             connect( action, SIGNAL(triggered()),_folderOpenActionMapper,SLOT(map()));
504             _folderOpenActionMapper->setMapping( action, folder->alias() );
505
506             _contextMenu->addAction(action);
507         }
508         _contextMenu->addAction(_actionAddFolder);
509     }
510
511     _contextMenu->addSeparator();
512     _contextMenu->addAction(_actionConfigure);
513     _contextMenu->addAction(_actionConfigureProxy);
514     _contextMenu->addSeparator();
515     _contextMenu->addAction(_actionAbout);
516     _contextMenu->addSeparator();
517
518     _contextMenu->addAction(_actionQuit);
519 }
520
521 void Application::setupLogBrowser()
522 {
523     // might be called from second instance
524     if (!_logBrowser) {
525         // init the log browser.
526         _logBrowser = new LogBrowser;
527         qInstallMsgHandler( mirallLogCatcher );
528         // ## TODO: allow new log name maybe?
529         if (!_logFile.isEmpty()) {
530             qDebug() << "Logging into logfile: " << _logFile << " with flush " << _logFlush;
531             _logBrowser->setLogFile( _logFile, _logFlush );
532         }
533     }
534
535     if (_showLogWindow)
536         slotOpenLogBrowser();
537
538     qDebug() << QString::fromLatin1( "################## %1 %2 %3 ").arg(_theme->appName())
539                 .arg( QLocale::system().name() )
540                 .arg(_theme->version());
541
542 }
543
544 void Application::setupProxy()
545 {
546     //
547     Mirall::MirallConfigFile cfg;
548     int proxy = cfg.proxyType();
549
550     switch(proxy) {
551     case QNetworkProxy::NoProxy: {
552         QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);
553         break;
554     }
555     case QNetworkProxy::DefaultProxy: {
556         QNetworkProxyFactory::setUseSystemConfiguration(true);
557         break;
558     }
559
560     case QNetworkProxy::Socks5Proxy: {
561         proxy = QNetworkProxy::HttpProxy;
562         cfg.setProxyType(proxy);
563         // fall through
564     }
565     case QNetworkProxy::HttpProxy:{
566         QNetworkProxy proxy;
567         proxy.setType(QNetworkProxy::HttpProxy);
568         proxy.setHostName(cfg.proxyHostName());
569         proxy.setPort(cfg.proxyPort());
570         proxy.setUser(cfg.proxyUser());
571         proxy.setPassword(cfg.proxyPassword());
572         QNetworkProxy::setApplicationProxy(proxy);
573         break;
574     }
575     }
576 }
577
578 /*
579  * open the folder with the given Alais
580  */
581 void Application::slotFolderOpenAction( const QString& alias )
582 {
583     Folder *f = _folderMan->folder(alias);
584     qDebug() << "opening local url " << f->path();
585     if( f ) {
586         QUrl url(f->path(), QUrl::TolerantMode);
587         url.setScheme( QLatin1String("file") );
588
589 #ifdef Q_OS_WIN32
590         // work around a bug in QDesktopServices on Win32, see i-net
591         QString filePath = f->path();
592
593         if (filePath.startsWith(QLatin1String("\\\\")) || filePath.startsWith(QLatin1String("//")))
594             url.setUrl(QDir::toNativeSeparators(filePath));
595         else
596             url = QUrl::fromLocalFile(filePath);
597 #endif
598         QDesktopServices::openUrl(url);
599     }
600 }
601
602 void Application::slotOpenOwnCloud()
603 {
604   MirallConfigFile cfgFile;
605
606   QString url = cfgFile.ownCloudUrl();
607   QDesktopServices::openUrl( url );
608 }
609
610 void Application::slotTrayClicked( QSystemTrayIcon::ActivationReason reason )
611 {
612     // A click on the tray icon should only open the status window on Win and
613     // Linux, not on Mac. They want a menu entry.
614     // If the user canceled login, rather open the login window.
615     if( CredentialStore::instance()->state() == CredentialStore::UserCanceled ||
616             CredentialStore::instance()->state() == CredentialStore::Error ) {
617         slotFetchCredentials();
618     }
619 #if defined Q_WS_WIN || defined Q_WS_X11
620     if( reason == QSystemTrayIcon::Trigger && _actionOpenStatus->isEnabled() ) {
621         slotOpenStatus();
622     }
623 #endif
624 }
625
626 void Application::slotAddFolder()
627 {
628   _folderMan->disableFoldersWithRestore();
629
630   Folder::Map folderMap = _folderMan->map();
631
632   _folderWizard->setFolderMap( &folderMap );
633
634   _folderWizard->restart();
635
636   if (_folderWizard->exec() == QDialog::Accepted) {
637     qDebug() << "* Folder wizard completed";
638
639     bool goodData = true;
640
641     QString alias        = _folderWizard->field(QLatin1String("alias")).toString();
642     QString sourceFolder = _folderWizard->field(QLatin1String("sourceFolder")).toString();
643     QString backend      = QLatin1String("csync");
644     QString targetPath;
645     bool onlyThisLAN = false;
646     bool onlyOnline  = false;
647
648     if (_folderWizard->field(QLatin1String("local?")).toBool()) {
649         // setup a local csync folder
650         targetPath = _folderWizard->field(QLatin1String("targetLocalFolder")).toString();
651     } else if (_folderWizard->field(QLatin1String("remote?")).toBool()) {
652         // setup a remote csync folder
653         targetPath  = _folderWizard->field(QLatin1String("targetURLFolder")).toString();
654         onlyOnline  = _folderWizard->field(QLatin1String("onlyOnline?")).toBool();
655         onlyThisLAN = _folderWizard->field(QLatin1String("onlyThisLAN?")).toBool();
656         (void) onlyOnline;
657         (void) onlyThisLAN;
658     } else if( _folderWizard->field(QLatin1String("OC?")).toBool() ||
659                Theme::instance()->singleSyncFolder()) {
660         // setup a ownCloud folder
661         backend    = QLatin1String("owncloud");
662         targetPath = _folderWizard->field(QLatin1String("targetOCFolder")).toString(); //empty in single folder mode
663     } else {
664       qWarning() << "* Folder not local and note remote?";
665       goodData = false;
666     }
667
668     if( goodData ) {
669         _folderMan->addFolderDefinition( backend, alias, sourceFolder, targetPath, onlyThisLAN );
670         Folder *f = _folderMan->setupFolderFromConfigFile( alias );
671         if( f ) {
672             _statusDialog->slotAddFolder( f );
673             _statusDialog->buttonsSetEnabled();
674             setupContextMenu();
675         }
676     }
677
678   } else {
679     qDebug() << "* Folder wizard cancelled";
680   }
681   _folderMan->restoreEnabledFolders();
682 }
683
684 void Application::slotOpenStatus()
685 {
686   if( ! _statusDialog ) return;
687
688   QWidget *raiseWidget = 0;
689
690   // check if there is a mirall.cfg already.
691   if( _owncloudSetupWizard->wizard()->isVisible() ) {
692     raiseWidget = _owncloudSetupWizard->wizard();
693   }
694
695   // if no config file is there, start the configuration wizard.
696   if( ! raiseWidget ) {
697     MirallConfigFile cfgFile;
698
699     if( !cfgFile.exists() ) {
700       qDebug() << "No configured folders yet, start the Owncloud integration dialog.";
701       _owncloudSetupWizard->startWizard(true); // with intro
702     } else {
703       qDebug() << "#============# Status dialog starting #=============#";
704       raiseWidget = _statusDialog;
705       _statusDialog->setFolderList( _folderMan->map() );
706     }
707   }
708
709   // viel hilft viel ;-)
710   if( raiseWidget ) {
711 #if defined(Q_WS_WIN) || defined (Q_OS_MAC)
712     Qt::WindowFlags eFlags = raiseWidget->windowFlags();
713     eFlags |= Qt::WindowStaysOnTopHint;
714     raiseWidget->setWindowFlags(eFlags);
715     raiseWidget->show();
716     eFlags &= ~Qt::WindowStaysOnTopHint;
717     raiseWidget->setWindowFlags(eFlags);
718 #endif
719     raiseWidget->show();
720     raiseWidget->raise();
721     raiseWidget->activateWindow();
722   }
723 }
724
725 void Application::slotOpenLogBrowser()
726 {
727     _logBrowser->show();
728     _logBrowser->raise();
729 }
730
731 void Application::slotAbout()
732 {
733     QString devString;
734 #ifdef GIT_SHA1
735     const QString githubPrefix(QLatin1String(
736                                "https://github.com/owncloud/mirall/commit/"));
737     const QString gitSha1(QLatin1String(GIT_SHA1));
738     devString = tr("<p><small>Built from Git revision <a href=\"%1\">%2</a>"
739                    " on %3, %4<br>using OCsync %5 and Qt %6.</small><p>")
740                        .arg(githubPrefix+gitSha1).arg(gitSha1.left(6))
741                        .arg(__DATE__).arg(__TIME__)
742                        .arg(MIRALL_STRINGIFY(LIBCSYNC_VERSION))
743                        .arg(QT_VERSION_STR);
744 #endif
745     QMessageBox::about(0, tr("About %1").arg(_theme->appName()),
746                        tr("<p><b>%1 Client Version %2</b></p>"
747                           "<p><b>Authors</b>"
748                           "<br><a href=\"mailto:freitag@owncloud.com\">"
749                           "Klaas Freitag</a>, ownCloud, Inc."
750                           "<br><a href=\"mailto:danimo@owncloud.com\">"
751                           "Daniel Molkentin</a>, ownCloud, Inc."
752                           "<br><br>Based on Mirall by Duncan Mac-Vicar P.</p>"
753                           "<p>For more information visit <a href=\"%3\">%4</a>.</p>"
754                           "%7"
755                           )
756                        .arg(_theme->appName())
757                        .arg(MIRALL_STRINGIFY(MIRALL_VERSION))
758                        .arg("http://" MIRALL_STRINGIFY(APPLICATION_DOMAIN))
759                        .arg(MIRALL_STRINGIFY(APPLICATION_DOMAIN))
760                        .arg(devString)
761                       );
762 }
763
764 /*
765   * the folder is to be removed. The slot is called from a signal emitted by
766   * the status dialog, which removes the folder from its list by itself.
767   */
768 void Application::slotRemoveFolder( const QString& alias )
769 {
770     int ret = QMessageBox::question( 0, tr("Confirm Folder Remove"),
771                                      tr("Do you really want to remove upload folder <i>%1</i>?").arg(alias),
772                                      QMessageBox::Yes|QMessageBox::No );
773
774     if( ret == QMessageBox::No ) {
775         return;
776     }
777     Folder *f = _folderMan->folder(alias);
778     if( f && _overallStatusStrings.contains( f->alias() )) {
779         _overallStatusStrings.remove( f->alias() );
780     }
781
782     _folderMan->slotRemoveFolder( alias );
783     _statusDialog->slotRemoveSelectedFolder( );
784     computeOverallSyncStatus();
785     setupContextMenu();
786 }
787
788 void Application::slotInfoFolder( const QString& alias )
789 {
790     qDebug() << "details of folder with alias " << alias;
791
792     SyncResult folderResult = _folderMan->syncResult( alias );
793
794     QString folderMessage;
795
796     SyncResult::Status syncStatus = folderResult.status();
797     switch( syncStatus ) {
798     case SyncResult::Undefined:
799         folderMessage = tr( "Undefined Folder State" );
800         break;
801     case SyncResult::NotYetStarted:
802         folderMessage = tr( "The folder waits to start syncing." );
803         break;
804     case SyncResult::SyncRunning:
805         folderMessage = tr("Sync is running.");
806         break;
807     case SyncResult::Success:
808         folderMessage = tr("Last Sync was successful.");
809         break;
810     case SyncResult::Error:
811         folderMessage = tr( "Syncing Error." );
812         break;
813     case SyncResult::SetupError:
814         folderMessage = tr( "Setup Error." );
815         break;
816     default:
817         folderMessage = tr( "Undefined Error State." );
818     }
819     folderMessage = QLatin1String("<b>") + folderMessage + QLatin1String("</b><br/>");
820
821     QMessageBox infoBox( QMessageBox::Information, tr( "Folder information" ), alias, QMessageBox::Ok );
822     QStringList li = folderResult.errorStrings();
823     foreach( const QString& l, li ) {
824         folderMessage += QString::fromLatin1("<p>%1</p>").arg( l );
825     }
826
827     infoBox.setText( folderMessage );
828
829     //    qDebug() << "informative text: " << infoBox.informativeText();
830
831     if ( !folderResult.syncChanges().isEmpty() ) {
832         QString details;
833         QHash < QString, QStringList > changes = folderResult.syncChanges();
834         QHash< QString, QStringList >::const_iterator change_it = changes.constBegin();
835         for(; change_it != changes.constEnd(); ++change_it ) {
836             QString changeType = tr( "Unknown" );
837             if ( change_it.key() == QLatin1String("changed") ) {
838             changeType = tr( "Changed files:\n" );
839             } else if ( change_it.key() == QLatin1String("added") ) {
840                 changeType = tr( "Added files:\n" );
841             } else if ( change_it.key() == QLatin1String("deleted") ) {
842             changeType = tr( "New files in the server, or files deleted locally:\n");
843             }
844
845             QStringList files = change_it.value();
846             QString fileList;
847                 foreach( const QString& file, files) {
848                     fileList += file + QLatin1Char('\n');
849             }
850             details += changeType + fileList;
851         }
852         infoBox.setDetailedText(details);
853         qDebug() << "detailed text: " << infoBox.detailedText();
854     }
855     infoBox.exec();
856 }
857
858 void Application::slotEnableFolder(const QString& alias, const bool enable)
859 {
860     qDebug() << "Application: enable folder with alias " << alias;
861     bool terminate = false;
862
863     // this sets the folder status to disabled but does not interrupt it.
864     Folder *f = _folderMan->folder( alias );
865     if( f && !enable ) {
866         // check if a sync is still running and if so, ask if we should terminate.
867         if( f->isBusy() ) { // its still running
868             int reply = QMessageBox::question( 0, tr("Sync Running"),
869                                                tr("The syncing operation is running.<br/>Do you want to terminate it?"),
870                                                QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes );
871             if ( reply == QMessageBox::Yes )
872                 terminate = true;
873             else
874                 return; // do nothing
875         }
876     }
877
878     // message box can return at any time while the thread keeps running,
879     // so better check again after the user has responded.
880     if ( f->isBusy() && terminate )
881         _folderMan->terminateSyncProcess( alias );
882
883     _folderMan->slotEnableFolder( alias, enable );
884     _statusDialog->slotUpdateFolderState( f );
885 }
886
887 void Application::slotConfigure()
888 {
889   _folderMan->disableFoldersWithRestore();
890   _owncloudSetupWizard->startWizard(false);
891   _folderMan->restoreEnabledFolders();
892 }
893
894 void Application::slotConfigureProxy()
895 {
896     ProxyDialog dlg;
897     if (dlg.exec() == QDialog::Accepted)
898     {
899         setupProxy();
900     }
901 }
902
903 void Application::slotParseOptions(const QString &opts)
904 {
905     QStringList options = opts.split(QLatin1Char('|'));
906     parseOptions(options);
907     setupLogBrowser();
908 }
909
910 void Application::slotShowTrayMessage(const QString &title, const QString &msg)
911 {
912     _tray->showMessage(title, msg);
913 }
914
915 void Application::slotSyncStateChange( const QString& alias )
916 {
917     SyncResult result = _folderMan->syncResult( alias );
918
919     // do not promote LocalSyncState to the status dialog.
920     if( !result.localRunOnly() ) {
921         _statusDialog->slotUpdateFolderState( _folderMan->folder(alias) );
922     }
923     computeOverallSyncStatus();
924
925     qDebug() << "Sync state changed for folder " << alias << ": "  << result.statusString();
926 }
927
928 void Application::parseOptions(const QStringList &options)
929 {
930     QStringListIterator it(options);
931     // skip file name;
932     if (it.hasNext()) it.next();
933
934     while (it.hasNext()) {
935         QString option = it.next();
936         if (option == QLatin1String("--help")) {
937             showHelp();
938         } else if (option == QLatin1String("--logwindow") ||
939                 option == QLatin1String("-l")) {
940             _showLogWindow = true;
941         } else if (option == QLatin1String("--logfile")) {
942             if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--"))) {
943                 _logFile = it.next();
944             } else {
945                 showHelp();
946             }
947         } else if (option == QLatin1String("--logflush")) {
948             _logFlush = true;
949         } else if (option == QLatin1String("--monoicons")) {
950             _theme->setSystrayUseMonoIcons(true); 
951         }
952     }
953 }
954
955 void Application::computeOverallSyncStatus()
956 {
957
958     // display the info of the least successful sync (eg. not just display the result of the latest sync
959     SyncResult overallResult(SyncResult::Undefined );
960     QString trayMessage;
961     Folder::Map map = _folderMan->map();
962
963     foreach ( Folder *syncedFolder, map.values() ) {
964         QString folderMessage = _overallStatusStrings[syncedFolder->alias()];
965
966         SyncResult folderResult = syncedFolder->syncResult();
967         SyncResult::Status syncStatus = folderResult.status();
968
969         if( ! folderResult.localRunOnly() ) { // skip local runs, use the last message.
970             switch( syncStatus ) {
971             case SyncResult::Undefined:
972                 if ( overallResult.status() != SyncResult::Error ) {
973                     overallResult.setStatus(SyncResult::Error);
974                 }
975                 folderMessage = tr( "Undefined State." );
976                 break;
977             case SyncResult::NotYetStarted:
978                 folderMessage = tr( "Waits to start syncing." );
979                 overallResult.setStatus( SyncResult::NotYetStarted );
980                 break;
981             case SyncResult::SyncRunning:
982                 folderMessage = tr( "Sync is running." );
983                 overallResult.setStatus( SyncResult::SyncRunning );
984                 break;
985             case SyncResult::Success:
986                 if( overallResult.status() == SyncResult::Undefined ) {
987                     folderMessage = tr( "Last Sync was successful." );
988                     overallResult.setStatus( SyncResult::Success );
989                 }
990                 break;
991             case SyncResult::Error:
992                 overallResult.setStatus( SyncResult::Error );
993                 folderMessage = tr( "Syncing Error." );
994                 break;
995             case SyncResult::SetupError:
996                 if ( overallResult.status() != SyncResult::Error ) {
997                     overallResult.setStatus( SyncResult::SetupError );
998                 }
999                 folderMessage = tr( "Setup Error." );
1000                 break;
1001             default:
1002                 folderMessage = tr( "Undefined Error State." );
1003                 overallResult.setStatus( SyncResult::Error );
1004             }
1005             if( !syncedFolder->syncEnabled() ) {
1006                 // sync is disabled.
1007                 folderMessage += tr( " (Sync is paused)" );
1008             }
1009         }
1010
1011         qDebug() << "Folder in overallStatus Message: " << syncedFolder << " with name " << syncedFolder->alias();
1012         QString msg = QString::fromLatin1("Folder %1: %2").arg(syncedFolder->alias()).arg(folderMessage);
1013         if( msg != _overallStatusStrings[syncedFolder->alias()] ) {
1014             _overallStatusStrings[syncedFolder->alias()] = msg;
1015         }
1016     }
1017
1018     // create the tray blob message, check if we have an defined state
1019     if( overallResult.status() != SyncResult::Undefined ) {
1020         QStringList allStatusStrings = _overallStatusStrings.values();
1021         if( ! allStatusStrings.isEmpty() )
1022             trayMessage = allStatusStrings.join(QLatin1String("\n"));
1023         else
1024             trayMessage = tr("No sync folders configured.");
1025
1026         QIcon statusIcon = _theme->syncStateIcon( overallResult.status(), true); // size 48 before
1027
1028         _tray->setIcon( statusIcon );
1029         _tray->setToolTip(trayMessage);
1030     }
1031 }
1032
1033 void Application::showHelp()
1034 {
1035     std::cout << _theme->appName().toLatin1().constData() << " version " <<
1036                  _theme->version().toLatin1().constData() << std::endl << std::endl;
1037     std::cout << "File synchronisation desktop utility." << std::endl << std::endl;
1038     std::cout << "Options:" << std::endl;
1039     std::cout << "  --logwindow          : open a window to show log output." << std::endl;
1040     std::cout << "  --logfile <filename> : write log output to file <filename>." << std::endl;
1041     std::cout << "  --logflush           : flush the log file after every write." << std::endl;
1042     std::cout << "  --monoicons          : Use black/white pictograms for systray." << std::endl;
1043     std::cout << std::endl;
1044     if (_theme->appName() == QLatin1String("ownCloud"))
1045         std::cout << "For more information, see http://www.owncloud.org" << std::endl;
1046     _helpOnly = true;
1047 }
1048
1049 bool Application::giveHelp()
1050 {
1051     return _helpOnly;
1052 }
1053 } // namespace Mirall
1054