1 #include <QtGlobal>
2 
3 #include <QtWidgets>
4 #include <QApplication>
5 #include <QDesktopServices>
6 #include <QFile>
7 #include <QTextStream>
8 #include <QDir>
9 #include <QCoreApplication>
10 #include <QMessageBox>
11 #include <QTimer>
12 #include <QHostInfo>
13 
14 #include <errno.h>
15 #include <glib.h>
16 
17 #include "utils/utils.h"
18 #include "utils/file-utils.h"
19 #include "utils/log.h"
20 #include "account-mgr.h"
21 #include "configurator.h"
22 #include "daemon-mgr.h"
23 #include "message-poller.h"
24 #include "settings-mgr.h"
25 #include "certs-mgr.h"
26 #include "rpc/rpc-client.h"
27 #include "ui/main-window.h"
28 #include "ui/tray-icon.h"
29 #include "ui/settings-dialog.h"
30 #include "ui/init-vdrive-dialog.h"
31 #include "ui/login-dialog.h"
32 #include "open-local-helper.h"
33 #include "avatar-service.h"
34 #include "filebrowser/thumbnail-service.h"
35 #include "filebrowser/data-cache.h"
36 #include "filebrowser/auto-update-mgr.h"
37 #include "filebrowser/data-mgr.h"
38 #include "rpc/local-repo.h"
39 #include "rpc/rpc-server.h"
40 #include "network-mgr.h"
41 #include "server-status-service.h"
42 #include "account-info-service.h"
43 #include "customization-service.h"
44 
45 #if defined(Q_OS_WIN32)
46     #include "ext-handler.h"
47     #include "utils/registry.h"
48 #elif defined(HAVE_FINDER_SYNC_SUPPORT)
49     #include "finder-sync/finder-sync-listener.h"
50 #endif
51 
52 #ifdef HAVE_SPARKLE_SUPPORT
53 #include "auto-update-service.h"
54 #endif
55 
56 
57 #if defined(Q_OS_MAC)
58 #include "utils/utils-mac.h"
59 #endif
60 
61 #include "seafile-applet.h"
62 
63 namespace {
64 enum DEBUG_LEVEL {
65   DEBUG = 0,
66   WARNING
67 };
68 
69 // -DQT_NO_DEBUG is used with cmake and qmake if it is a release build
70 // if it is debug build, use DEBUG level as default
71 #if !defined(QT_NO_DEBUG) || !defined(NDEBUG)
72 DEBUG_LEVEL seafile_client_debug_level = DEBUG;
73 #else
74 // if it is release build, use WARNING level as default
75 DEBUG_LEVEL seafile_client_debug_level = WARNING;
76 #endif
77 
myLogHandlerDebug(QtMsgType type,const QMessageLogContext & context,const QString & msg)78 void myLogHandlerDebug(QtMsgType type, const QMessageLogContext &context, const QString &msg)
79 {
80     QByteArray localMsg = msg.toLocal8Bit();
81     switch (type) {
82 // Note: By default, this information (QMessageLogContext) is recorded only in debug builds.
83 // You can overwrite this explicitly by defining QT_MESSAGELOGCONTEXT or QT_NO_MESSAGELOGCONTEXT.
84 // from http://doc.qt.io/qt-5/qmessagelogcontext.html
85 #ifdef QT_MESSAGELOGCONTEXT
86     case QtDebugMsg:
87         g_debug("%s (%s:%u)\n", localMsg.constData(), context.file, context.line);
88         break;
89     case QtWarningMsg:
90         g_warning("%s (%s:%u)\n", localMsg.constData(), context.file, context.line);
91         break;
92     case QtCriticalMsg:
93         g_critical("%s (%s:%u)\n", localMsg.constData(), context.file, context.line);
94         break;
95     case QtFatalMsg:
96         g_critical("%s (%s:%u)\n", localMsg.constData(), context.file, context.line);
97         abort();
98 #else // QT_MESSAGELOGCONTEXT
99     case QtDebugMsg:
100         g_debug("%s\n", localMsg.constData());
101         break;
102     case QtWarningMsg:
103         g_warning("%s\n", localMsg.constData());
104         break;
105     case QtCriticalMsg:
106         g_critical("%s\n", localMsg.constData());
107         break;
108     case QtFatalMsg:
109         g_critical("%s\n", localMsg.constData());
110         abort();
111 #endif // QT_MESSAGELOGCONTEXT
112     default:
113         break;
114     }
115 }
myLogHandler(QtMsgType type,const QMessageLogContext & context,const QString & msg)116 void myLogHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
117 {
118     QByteArray localMsg = msg.toLocal8Bit();
119     switch (type) {
120 #ifdef QT_MESSAGELOGCONTEXT
121     case QtWarningMsg:
122         g_warning("%s (%s:%u)\n", localMsg.constData(), context.file, context.line);
123         break;
124     case QtCriticalMsg:
125         g_critical("%s (%s:%u)\n", localMsg.constData(), context.file, context.line);
126         break;
127     case QtFatalMsg:
128         g_critical("%s (%s:%u)\n", localMsg.constData(), context.file, context.line);
129         abort();
130 #else // QT_MESSAGELOGCONTEXT
131     case QtWarningMsg:
132         g_warning("%s\n", localMsg.constData());
133         break;
134     case QtCriticalMsg:
135         g_critical("%s\n", localMsg.constData());
136         break;
137     case QtFatalMsg:
138         g_critical("%s\n", localMsg.constData());
139         abort();
140 #endif // QT_MESSAGELOGCONTEXT
141     default:
142         break;
143     }
144 }
145 
146 #ifdef Q_OS_MAC
writeCABundleForCurl()147 void writeCABundleForCurl()
148 {
149     QString ca_bundle_path = QDir(seafApplet->configurator()->seafileDir()).filePath("ca-bundle.pem");
150     QFile bundle(ca_bundle_path);
151     if (bundle.exists()) {
152         bundle.remove();
153     }
154     bundle.open(QIODevice::WriteOnly);
155     const std::vector<QByteArray> certs = utils::mac::getSystemCaCertificates();
156     for (size_t i = 0; i < certs.size(); i++) {
157         QList<QSslCertificate> list = QSslCertificate::fromData(certs[i], QSsl::Der);
158         foreach (const QSslCertificate& cert, list) {
159             bundle.write(cert.toPem());
160         }
161     }
162 }
163 #endif
164 
165 
166 const char *const kPreconfigureUsername = "PreconfigureUsername";
167 const char *const kPreconfigureUserToken = "PreconfigureUserToken";
168 const char *const kPreconfigureServerAddr = "PreconfigureServerAddr";
169 const char *const kPreconfigureComputerName = "PreconfigureComputerName";
170 const char* const kPreConfiguretionBlockSize = "PreconfigureBlockSize";
171 const char* const kHideConfigurationWizard = "HideConfigurationWizard";
172 #if defined(Q_OS_WIN32)
173 const char *const kSeafileConfigureFileName = "seafile.ini";
174 const char *const kSeafileConfigurePath = "SOFTWARE\\Seafile";
175 const int kIntervalBeforeShowInitVirtualDialog = 3000;
176 #else
177 const char *const kSeafileConfigureFileName = ".seafilerc";
178 #endif
179 const char *const kSeafilePreconfigureGroupName = "preconfigure";
180 
181 const int kIntervalForUpdateRepoProperty = 1000;
182 
183 const char *kRepoServerUrlProperty = "server-url";
184 
185 } // namespace
186 
187 
188 SeafileApplet *seafApplet;
189 
SeafileApplet()190 SeafileApplet::SeafileApplet()
191     : configurator_(new Configurator),
192       account_mgr_(new AccountManager),
193       daemon_mgr_(new DaemonManager),
194       main_win_(NULL),
195       rpc_client_(new SeafileRpcClient),
196       message_poller_(new MessagePoller),
197       settings_dialog_(new SettingsDialog),
198       settings_mgr_(new SettingsManager),
199       certs_mgr_(new CertsManager),
200       data_mgr_(new DataManager),
201       started_(false),
202       in_exit_(false),
203       is_pro_(false),
204       about_to_quit_(false)
205 {
206     tray_icon_ = new SeafileTrayIcon(this);
207     connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(onAboutToQuit()));
208 }
209 
~SeafileApplet()210 SeafileApplet::~SeafileApplet()
211 {
212     NetworkStatusDetector::instance()->stop();
213 
214 #ifdef HAVE_FINDER_SYNC_SUPPORT
215     finderSyncListenerStop();
216 #endif
217     delete tray_icon_;
218     delete certs_mgr_;
219     delete settings_dialog_;
220     delete message_poller_;
221     delete rpc_client_;
222     delete account_mgr_;
223     // seafile-applet exit will inform seaf-daemon to clean sync token,
224     // so the class object deamon_mgr daemon_mgr_ dealloc after account_mgr_.
225 
226     delete daemon_mgr_;
227     delete configurator_;
228     delete data_mgr_;
229     if (main_win_)
230         delete main_win_;
231 #if defined(Q_OS_WIN32)
232     SeafileExtensionHandler::instance()->stop();
233 
234 #ifdef HAVE_SPARKLE_SUPPORT
235     AutoUpdateService::instance()->stop();
236 #endif
237 
238 #endif
239 }
240 
start()241 void SeafileApplet::start()
242 {
243     refreshQss();
244 
245     configurator_->checkInit();
246 
247     initLog();
248 
249     qDebug("client id is %s", toCStr(getUniqueClientId()));
250     account_mgr_->start();
251 
252     certs_mgr_->start();
253 
254     data_mgr_->start();
255 
256 #if defined(Q_OS_WIN32)
257     QString crash_rpt_path = QDir(configurator_->ccnetDir()).filePath("logs/seafile-crash-report.txt");
258     if (!g_setenv ("CRASH_RPT_PATH", toCStr(crash_rpt_path), FALSE))
259         qWarning("Failed to set CRASH_RPT_PATH env variable.\n");
260 #endif
261 
262 #if defined(Q_OS_MAC)
263     writeCABundleForCurl();
264 #endif
265 
266     // Load system proxy information. This must be done before we start
267     // seaf-daemon.
268     settings_mgr_->writeSystemProxyInfo(
269         account_mgr_->currentAccount().serverUrl,
270         QDir(configurator_->seafileDir()).filePath("system-proxy.txt"));
271 
272     FileCache::instance()->start();
273 
274     //
275     // start daemons
276     //
277     daemon_mgr_->startSeafileDaemon();
278 
279     connect(daemon_mgr_, SIGNAL(daemonStarted()),
280             this, SLOT(onDaemonStarted()));
281     connect(daemon_mgr_, SIGNAL(daemonRestarted()),
282             this, SLOT(onDaemonRestarted()));
283 }
284 
onDaemonStarted()285 void SeafileApplet::onDaemonStarted()
286 {
287     //
288     // start daemon-related services
289     //
290     rpc_client_->connectDaemon();
291 
292     // Sleep 500 millseconds to wait seafile registering services
293 
294     msleep(500);
295 
296     //
297     // load proxy settings (important)
298     //
299     settings_mgr_->loadSettings();
300 
301     //
302     // start network-related services
303     //
304     NetworkStatusDetector::instance()->start();
305     AutoUpdateManager::instance()->start();
306 
307     AvatarService::instance()->start();
308     ThumbnailService::instance()->start();
309 
310     ServerStatusService::instance()->start();
311     CustomizationService::instance()->start();
312     AccountInfoService::instance()->start();
313     SeafileAppletRpcServer::instance()->start();
314 
315     account_mgr_->updateServerInfoForAllAccounts();
316 
317     //
318     // start ui part
319     //
320     main_win_ = new MainWindow;
321 
322 #if defined(Q_OS_MAC)
323     seafApplet->settingsManager()->setHideDockIcon(seafApplet->settingsManager()->hideDockIcon());
324 #endif
325 
326 #ifdef XCODE_APP
327     if (configurator_->firstUse()) {
328         settings_mgr_->setAutoStart(true);
329     }
330 #endif
331 
332     if (configurator_->firstUse() || account_mgr_->accounts().size() == 0) {
333         do {
334             QString username = readPreconfigureExpandedString(kPreconfigureUsername);
335             QString token = readPreconfigureExpandedString(kPreconfigureUserToken);
336             QString url = readPreconfigureExpandedString(kPreconfigureServerAddr);
337             QString computer_name = readPreconfigureExpandedString(kPreconfigureComputerName, settingsManager()->getComputerName());
338             if (!computer_name.isEmpty())
339                 settingsManager()->setComputerName(computer_name);
340             if (!username.isEmpty() && !token.isEmpty() && !url.isEmpty()) {
341                 Account account(url, username, token);
342                 account_mgr_->setCurrentAccount(account);
343                 break;
344             }
345 
346             if (readPreconfigureEntry(kHideConfigurationWizard).toInt())
347                 break;
348             LoginDialog login_dialog;
349             login_dialog.exec();
350         } while (0);
351     } else if (!account_mgr_->accounts().empty()) {
352         const Account &account = account_mgr_->accounts()[0];
353         account_mgr_->removeNonautoLoginSyncTokens();
354         account_mgr_->validateAndUseAccount(account);
355     }
356 
357     started_ = true;
358 
359     if (configurator_->firstUse() || !settings_mgr_->hideMainWindowWhenStarted()) {
360         main_win_->showWindow();
361     }
362 
363     tray_icon_->start();
364     tray_icon_->setState(SeafileTrayIcon::STATE_DAEMON_UP);
365     message_poller_->start();
366 
367 
368 #if defined(Q_OS_WIN32)
369     QTimer::singleShot(kIntervalBeforeShowInitVirtualDialog, this, SLOT(checkInitVDrive()));
370     configurator_->installCustomUrlHandler();
371 #endif
372 
373     QString value;
374     if (seafApplet->rpcClient()->seafileGetConfig("client_name", &value) < 0 || value.isEmpty()) {
375         // We do this because clients before 6.0 don't set the "client_name" option.
376         seafApplet->rpcClient()->seafileSetConfig(
377         "client_name", settings_mgr_->getComputerName());
378     }
379 
380     // Set the device id to the daemon so it can use it when generating commits.
381     // The "client_name" is not set here, but updated each time we call
382     // switch_account rpc.
383     if (rpc_client_->seafileGetConfig("client_id", &value) < 0 ||
384         value.isEmpty() || value != getUniqueClientId()) {
385         rpc_client_->seafileSetConfig("client_id", getUniqueClientId());
386     }
387 
388     // pre-configure option to set the size of sync block.
389     QString block = readPreconfigureExpandedString(kPreConfiguretionBlockSize);
390     if (!block.isEmpty()) {
391         int block_size = block.toInt();
392         if (rpc_client_->seafileSetConfigInt("block_size", block_size) < 0) {
393             qDebug("setting sync block_size error");
394         }
395     }
396     QTimer::singleShot(kIntervalForUpdateRepoProperty,
397                        this, SLOT(updateReposPropertyForHttpSync()));
398 
399     //
400     // start finder/explorer extension handler
401     //
402 #if defined(Q_OS_WIN32)
403     SeafileExtensionHandler::instance()->start();
404 #elif defined(HAVE_FINDER_SYNC_SUPPORT)
405     finderSyncListenerStart();
406 #endif
407 
408 #ifdef HAVE_SPARKLE_SUPPORT
409     if (AutoUpdateService::instance()->shouldSupportAutoUpdate()) {
410         AutoUpdateService::instance()->start();
411     }
412 #endif
413 }
414 
checkInitVDrive()415 void SeafileApplet::checkInitVDrive()
416 {
417     if (configurator_->firstUse() && account_mgr_->hasAccount()) {
418         const Account account = account_mgr_->currentAccount();
419         InitVirtualDriveDialog *dialog = new InitVirtualDriveDialog(account);
420         // Move the dialog to the left of the main window
421         int x = main_win_->pos().x() - dialog->rect().width() - 30;
422         int y = (QApplication::desktop()->screenGeometry().center() - dialog->rect().center()).y();
423         dialog->move(qMax(0, x), y);
424         dialog->show();
425         dialog->raise();
426         dialog->activateWindow();
427     }
428 }
429 
onAboutToQuit()430 void SeafileApplet::onAboutToQuit()
431 {
432     about_to_quit_ = true;
433     tray_icon_->hide();
434     if (main_win_) {
435         main_win_->writeSettings();
436     }
437 }
438 // stop the main event loop and return to the main function
errorAndExit(const QString & error)439 void SeafileApplet::errorAndExit(const QString& error)
440 {
441     if (in_exit_ || QCoreApplication::closingDown()) {
442         return;
443     }
444 
445     in_exit_ = true;
446 
447     warningBox(error);
448     // stop eventloop before exit and return to the main function
449     QCoreApplication::exit(1);
450 }
451 
restartApp()452 void SeafileApplet::restartApp()
453 {
454     if (in_exit_ || QCoreApplication::closingDown()) {
455         return;
456     }
457 
458     in_exit_ = true;
459 
460     QStringList args = QApplication::arguments();
461 
462     args.removeFirst();
463 
464     // append delay argument
465     bool found = false;
466     Q_FOREACH(const QString& arg, args)
467     {
468         if (arg == "--delay" || arg == "-D") {
469             found = true;
470             break;
471         }
472     }
473 
474     if (!found)
475         args.push_back("--delay");
476 
477     QProcess::startDetached(QApplication::applicationFilePath(), args);
478     QCoreApplication::quit();
479 }
480 
initLog()481 void SeafileApplet::initLog()
482 {
483     if (applet_log_init(toCStr(configurator_->ccnetDir())) < 0) {
484         errorAndExit(tr("Failed to initialize log: %s").arg(g_strerror(errno)));
485     } else {
486         // give a change to override DEBUG_LEVEL by environment
487         QString debug_level = qgetenv("SEAFILE_CLIENT_DEBUG");
488         if (!debug_level.isEmpty() && debug_level != "false" &&
489             debug_level != "0")
490             seafile_client_debug_level = DEBUG;
491 
492         if (seafile_client_debug_level == DEBUG)
493             qInstallMessageHandler(myLogHandlerDebug);
494         else
495             qInstallMessageHandler(myLogHandler);
496     }
497 }
498 
loadQss(const QString & path)499 bool SeafileApplet::loadQss(const QString& path)
500 {
501     QFile file(path);
502     if (!QFileInfo(file).exists()) {
503         return false;
504     }
505     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
506         return false;
507     }
508 
509     QTextStream input(&file);
510     style_ += "\n";
511     style_ += input.readAll();
512     qApp->setStyleSheet(style_);
513 
514     return true;
515 }
516 
refreshQss()517 void SeafileApplet::refreshQss()
518 {
519     style_.clear();
520     loadQss("qt.css") || loadQss(":/qt.css");
521 
522 #if defined(Q_OS_WIN32)
523     loadQss("qt-win.css") || loadQss(":/qt-win.css");
524 #elif defined(Q_OS_LINUX)
525     loadQss("qt-linux.css") || loadQss(":/qt-linux.css");
526 #else
527     loadQss("qt-mac.css") || loadQss(":/qt-mac.css");
528 #endif
529 }
530 
warningBox(const QString & msg,QWidget * parent)531 void SeafileApplet::warningBox(const QString& msg, QWidget *parent)
532 {
533     QMessageBox box(parent ? parent : main_win_);
534     box.setText(msg);
535     box.setWindowTitle(getBrand());
536     box.setIcon(QMessageBox::Warning);
537     box.addButton(tr("OK"), QMessageBox::YesRole);
538     box.exec();
539 
540     if (!parent && main_win_) {
541         main_win_->showWindow();
542     }
543     qWarning("%s", msg.toUtf8().data());
544 }
545 
messageBox(const QString & msg,QWidget * parent)546 void SeafileApplet::messageBox(const QString& msg, QWidget *parent)
547 {
548     QMessageBox box(parent ? parent : main_win_);
549     box.setText(msg);
550     box.setWindowTitle(getBrand());
551     box.setIcon(QMessageBox::Information);
552     box.addButton(tr("OK"), QMessageBox::YesRole);
553     if (!parent) {
554         main_win_->showWindow();
555     }
556     box.exec();
557     qDebug("%s", msg.toUtf8().data());
558 }
559 
yesOrNoBox(const QString & msg,QWidget * parent,bool default_val)560 bool SeafileApplet::yesOrNoBox(const QString& msg, QWidget *parent, bool default_val)
561 {
562     QMessageBox box(parent ? parent : main_win_);
563     box.setText(msg);
564     box.setWindowTitle(getBrand());
565     box.setIcon(QMessageBox::Question);
566     QPushButton *yes_btn = box.addButton(tr("Yes"), QMessageBox::YesRole);
567     QPushButton *no_btn = box.addButton(tr("No"), QMessageBox::NoRole);
568     box.setDefaultButton(default_val ? yes_btn: no_btn);
569     box.exec();
570 
571     return box.clickedButton() == yes_btn;
572 }
573 
yesOrCancelBox(const QString & msg,QWidget * parent,bool default_yes)574 bool SeafileApplet::yesOrCancelBox(const QString& msg, QWidget *parent, bool default_yes)
575 {
576     QMessageBox box(parent ? parent : main_win_);
577     box.setText(msg);
578     box.setWindowTitle(getBrand());
579     box.setIcon(QMessageBox::Question);
580     QPushButton *yes_btn = box.addButton(tr("Yes"), QMessageBox::YesRole);
581     QPushButton *cancel_btn = box.addButton(tr("Cancel"), QMessageBox::RejectRole);
582     box.setDefaultButton(default_yes ? yes_btn: cancel_btn);
583     box.exec();
584 
585     return box.clickedButton() == yes_btn;
586 }
587 
588 
589 QMessageBox::StandardButton
yesNoCancelBox(const QString & msg,QWidget * parent,QMessageBox::StandardButton default_btn)590 SeafileApplet::yesNoCancelBox(const QString& msg, QWidget *parent, QMessageBox::StandardButton default_btn)
591 {
592     QMessageBox box(parent ? parent : main_win_);
593     box.setText(msg);
594     box.setWindowTitle(getBrand());
595     box.setIcon(QMessageBox::Question);
596     QPushButton *yes_btn = box.addButton(tr("Yes"), QMessageBox::YesRole);
597     QPushButton *no_btn = box.addButton(tr("No"), QMessageBox::NoRole);
598     box.addButton(tr("Cancel"), QMessageBox::RejectRole);
599     box.setDefaultButton(default_btn);
600     box.exec();
601 
602     QAbstractButton *btn = box.clickedButton();
603     if (btn == yes_btn) {
604         return QMessageBox::Yes;
605     } else if (btn == no_btn) {
606         return QMessageBox::No;
607     }
608 
609     return QMessageBox::Cancel;
610 }
611 
detailedYesOrNoBox(const QString & msg,const QString & detailed_text,QWidget * parent,bool default_val)612 bool SeafileApplet::detailedYesOrNoBox(const QString& msg, const QString& detailed_text, QWidget *parent, bool default_val)
613 {
614     QMessageBox msgBox(QMessageBox::Question,
615                        getBrand(),
616                        msg,
617                        QMessageBox::Yes | QMessageBox::No,
618                        parent != 0 ? parent : main_win_);
619     msgBox.setDetailedText(detailed_text);
620     msgBox.setButtonText(QMessageBox::Yes, tr("Yes"));
621     msgBox.setButtonText(QMessageBox::No, tr("No"));
622     // Turns out the layout box in the QMessageBox is a grid
623     // You can force the resize using a spacer this way:
624     QSpacerItem* horizontalSpacer = new QSpacerItem(400, 0, QSizePolicy::Minimum, QSizePolicy::Expanding);
625     QGridLayout* layout = (QGridLayout*)msgBox.layout();
626     layout->addItem(horizontalSpacer, layout->rowCount(), 0, 1, layout->columnCount());
627     msgBox.setDefaultButton(default_val ? QMessageBox::Yes : QMessageBox::No);
628     return msgBox.exec() == QMessageBox::Yes;
629 }
630 
631 /**
632  * For each repo, add the "server-url" property (inferred from account url),
633  * which would be used for http sync.
634  */
updateReposPropertyForHttpSync()635 void SeafileApplet::updateReposPropertyForHttpSync()
636 {
637     std::vector<LocalRepo> repos;
638     if (rpc_client_->listLocalRepos(&repos) < 0) {
639         QTimer::singleShot(kIntervalForUpdateRepoProperty,
640                            this, SLOT(updateReposPropertyForHttpSync()));
641         return;
642     }
643 
644     const std::vector<Account>& accounts = account_mgr_->accounts();
645     for (size_t i = 0; i < repos.size(); i++) {
646         const LocalRepo& repo = repos[i];
647         QString repo_server_url;
648         QString server_url;
649         if (rpc_client_->getRepoProperty(repo.id, kRepoServerUrlProperty, &repo_server_url) < 0) {
650             continue;
651         }
652         if (!repo_server_url.isEmpty()) {
653             continue;
654         }
655         if (rpc_client_->getRepoProperty(repo.id, kRepoServerUrlProperty, &server_url) < 0) {
656             continue;
657         }
658 
659         QString server_host = QUrl(server_url).host();
660         for (size_t i = 0; i < accounts.size(); i++) {
661             const Account& account = accounts[i];
662             if (account.serverUrl.host() == server_host) {
663                 QUrl url(account.serverUrl);
664                 url.setPath("/");
665                 rpc_client_->setRepoProperty(repo.id, kRepoServerUrlProperty, url.toString());
666                 break;
667             }
668         }
669     }
670 }
671 
readPreconfigureEntry(const QString & key,const QVariant & default_value)672 QVariant SeafileApplet::readPreconfigureEntry(const QString& key, const QVariant& default_value)
673 {
674 #ifdef Q_OS_WIN32
675     QVariant v = RegElement::getPreconfigureValue(key);
676     if (!v.isNull()) {
677         return v;
678     }
679 #endif
680     QString configure_file = QDir::home().filePath(kSeafileConfigureFileName);
681     if (!QFileInfo(configure_file).exists())
682         return default_value;
683     QSettings setting(configure_file, QSettings::IniFormat);
684     setting.beginGroup(kSeafilePreconfigureGroupName);
685     QVariant value = setting.value(key, default_value);
686     setting.endGroup();
687     return value;
688 }
689 
readPreconfigureExpandedString(const QString & key,const QString & default_value)690 QString SeafileApplet::readPreconfigureExpandedString(const QString& key, const QString& default_value)
691 {
692     QVariant retval = readPreconfigureEntry(key, default_value);
693     if (retval.isNull() || retval.type() != QVariant::String)
694         return QString();
695     return expandVars(retval.toString());
696 }
697 
698 
getText(QWidget * parent,const QString & title,const QString & label,QLineEdit::EchoMode mode,const QString & text,bool * ok,Qt::WindowFlags flags,Qt::InputMethodHints inputMethodHints)699 QString SeafileApplet::getText(QWidget *parent,
700                                const QString &title,
701                                const QString &label,
702                                QLineEdit::EchoMode mode,
703                                const QString &text,
704                                bool *ok,
705                                Qt::WindowFlags flags,
706                                Qt::InputMethodHints inputMethodHints)
707 {
708     QInputDialog tmp_dialog;
709     // Get rid of the help button
710     if (flags == 0) {
711         flags = tmp_dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint;
712     }
713 
714     return QInputDialog::getText(parent != nullptr ? parent : main_win_,
715                                  title,
716                                  label,
717                                  mode,
718                                  text,
719                                  ok,
720                                  flags,
721                                  inputMethodHints);
722 }
723 
getUniqueClientId()724 QString SeafileApplet::getUniqueClientId()
725 {
726     static QString id;
727     if (!id.isEmpty()) {
728         return id;
729     }
730 
731     // Id file path is `~/Seafile/.seafile-data/id`
732     QFile id_file(QDir(seafApplet->configurator()->seafileDir())
733                   .absoluteFilePath("id"));
734     if (!id_file.exists()) {
735         qWarning("id file not found, creating it");
736         // First migrate existing id from ccnet.conf General.ID
737         QString ccnet_conf_file = QDir(configurator_->ccnetDir()).absoluteFilePath("ccnet.conf");
738         QString ccnet_id;
739         if (QFile(ccnet_conf_file).exists()) {
740             QSettings ccnet_conf(ccnet_conf_file, QSettings::IniFormat);
741             ccnet_id = ccnet_conf.value("ID").toString();
742             if (ccnet_id.isEmpty()) {
743                 ccnet_conf.beginGroup("General");
744                 ccnet_id = ccnet_conf.value("ID", "").toString();
745             }
746         }
747 
748         if (!ccnet_id.isEmpty()) {
749             id = ccnet_id;
750             qWarning("use existing ccnet id %s", toCStr(id));
751         } else {
752             srand(time(NULL));
753             while (id.length() < 40) {
754                 int r = rand() % 0xff;
755                 id += QString("%1").arg(r, 0, 16);
756             }
757             id = id.mid(0, 40);
758             qWarning("generated new device id %s", toCStr(id));
759         }
760 
761         if (!id_file.open(QIODevice::WriteOnly)) {
762             errorAndExit(tr("failed to save client id"));
763             return "";
764         }
765 
766         id_file.write(id.toUtf8().data());
767         return id;
768     }
769 
770     if (!id_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
771         errorAndExit(tr("failed to access %1").arg(id_file.fileName()));
772         return "";
773     }
774 
775     QTextStream input(&id_file);
776     input.setCodec("UTF-8");
777 
778     if (input.atEnd()) {
779         errorAndExit(tr("incorrect client id"));
780         return "";
781     }
782 
783     id = input.readLine().trimmed();
784     if (id.length() != 40) {
785         errorAndExit(tr("failed to read %1").arg(id_file.fileName()));
786         return "";
787     }
788 
789     qWarning("read id from id file");
790     return id;
791 }
792 
onDaemonRestarted()793 void SeafileApplet::onDaemonRestarted()
794 {
795     qDebug("reviving rpc client when daemon is restarted");
796     if (rpc_client_) {
797         delete rpc_client_;
798     }
799 
800     rpc_client_ = new SeafileRpcClient();
801     rpc_client_->tryConnectDaemon();
802 }
803