1 /*
2  * Copyright (C) by Klaas Freitag <freitag@owncloud.com>
3  * Copyright (C) by Julius Härtl <jus@bitgrid.net>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13  * for more details.
14  */
15 
16 #include <glib.h>
17 #include <gio/gio.h>
18 #include <cloudprovidersaccountexporter.h>
19 #include <cloudprovidersproviderexporter.h>
20 
21 #include "cloudproviderwrapper.h"
22 #include <account.h>
23 #include <folder.h>
24 #include <accountstate.h>
25 #include <QDesktopServices>
26 #include "openfilemanager.h"
27 #include "owncloudgui.h"
28 #include "application.h"
29 
30 using namespace OCC;
31 
32 GSimpleActionGroup *actionGroup = nullptr;
33 
CloudProviderWrapper(QObject * parent,Folder * folder,int folderId,CloudProvidersProviderExporter * cloudprovider)34 CloudProviderWrapper::CloudProviderWrapper(QObject *parent, Folder *folder, int folderId, CloudProvidersProviderExporter* cloudprovider) : QObject(parent)
35   , _folder(folder)
36 {
37     GMenuModel *model;
38     GActionGroup *action_group;
39     QString accountName = QString("Folder/%1").arg(folderId);
40 
41     _cloudProvider = CLOUD_PROVIDERS_PROVIDER_EXPORTER(cloudprovider);
42     _cloudProviderAccount = cloud_providers_account_exporter_new(_cloudProvider, accountName.toUtf8().data());
43 
44     cloud_providers_account_exporter_set_name (_cloudProviderAccount, folder->shortGuiLocalPath().toUtf8().data());
45     cloud_providers_account_exporter_set_icon (_cloudProviderAccount, g_icon_new_for_string(APPLICATION_ICON_NAME, nullptr));
46     cloud_providers_account_exporter_set_path (_cloudProviderAccount, folder->cleanPath().toUtf8().data());
47     cloud_providers_account_exporter_set_status (_cloudProviderAccount, CLOUD_PROVIDERS_ACCOUNT_STATUS_IDLE);
48     model = getMenuModel();
49     cloud_providers_account_exporter_set_menu_model (_cloudProviderAccount, model);
50     action_group = getActionGroup();
51     cloud_providers_account_exporter_set_action_group (_cloudProviderAccount, action_group);
52 
53     connect(ProgressDispatcher::instance(), SIGNAL(progressInfo(QString, ProgressInfo)), this, SLOT(slotUpdateProgress(QString, ProgressInfo)));
54     connect(_folder, SIGNAL(syncStarted()), this, SLOT(slotSyncStarted()));
55     connect(_folder, SIGNAL(syncFinished(SyncResult)), this, SLOT(slotSyncFinished(const SyncResult)));
56     connect(_folder, SIGNAL(syncPausedChanged(Folder*,bool)), this, SLOT(slotSyncPausedChanged(Folder*, bool)));
57 
58     _paused = _folder->syncPaused();
59     updatePauseStatus();
60     g_clear_object (&model);
61     g_clear_object (&action_group);
62 }
63 
~CloudProviderWrapper()64 CloudProviderWrapper::~CloudProviderWrapper()
65 {
66     g_object_unref(_cloudProviderAccount);
67     g_object_unref(_mainMenu);
68     g_object_unref(actionGroup);
69     g_object_unref(_recentMenu);
70 }
71 
accountExporter()72 CloudProvidersAccountExporter* CloudProviderWrapper::accountExporter()
73 {
74     return _cloudProviderAccount;
75 }
76 
shouldShowInRecentsMenu(const SyncFileItem & item)77 static bool shouldShowInRecentsMenu(const SyncFileItem &item)
78 {
79     return !Progress::isIgnoredKind(item._status)
80             && item._instruction != CSYNC_INSTRUCTION_EVAL
81             && item._instruction != CSYNC_INSTRUCTION_NONE;
82 }
83 
menu_item_new(const QString & label,const gchar * detailed_action)84 static GMenuItem *menu_item_new(const QString &label, const gchar *detailed_action)
85 {
86     return g_menu_item_new(label.toUtf8 ().data(), detailed_action);
87 }
88 
menu_item_new_submenu(const QString & label,GMenuModel * submenu)89 static GMenuItem *menu_item_new_submenu(const QString &label, GMenuModel *submenu)
90 {
91     return g_menu_item_new_submenu(label.toUtf8 ().data(), submenu);
92 }
93 
slotUpdateProgress(const QString & folder,const ProgressInfo & progress)94 void CloudProviderWrapper::slotUpdateProgress(const QString &folder, const ProgressInfo &progress)
95 {
96     // Only update progress for the current folder
97     Folder *f = FolderMan::instance()->folder(folder);
98     if (f != _folder)
99         return;
100 
101     // Build recently changed files list
102     if (!progress._lastCompletedItem.isEmpty() && shouldShowInRecentsMenu(progress._lastCompletedItem)) {
103         QString kindStr = Progress::asResultString(progress._lastCompletedItem);
104         QString timeStr = QTime::currentTime().toString("hh:mm");
105         QString actionText = tr("%1 (%2, %3)").arg(progress._lastCompletedItem._file, kindStr, timeStr);
106         if (f) {
107             QString fullPath = f->path() + '/' + progress._lastCompletedItem._file;
108             if (QFile(fullPath).exists()) {
109                 if (_recentlyChanged.length() > 5)
110                     _recentlyChanged.removeFirst();
111                 _recentlyChanged.append(qMakePair(actionText, fullPath));
112             } else {
113                 _recentlyChanged.append(qMakePair(actionText, QString("")));
114             }
115         }
116 
117     }
118 
119     // Build status details text
120     QString msg;
121     if (!progress._currentDiscoveredRemoteFolder.isEmpty()) {
122         msg =  tr("Checking for changes in \"%1\"").arg(progress._currentDiscoveredRemoteFolder);
123     } else if (progress.totalSize() == 0) {
124         qint64 currentFile = progress.currentFile();
125         qint64 totalFileCount = qMax(progress.totalFiles(), currentFile);
126         if (progress.trustEta()) {
127             msg = tr("Syncing %1 of %2  (%3 left)")
128                     .arg(currentFile)
129                     .arg(totalFileCount)
130                     .arg(Utility::durationToDescriptiveString2(progress.totalProgress().estimatedEta));
131         } else {
132             msg = tr("Syncing %1 of %2")
133                     .arg(currentFile)
134                     .arg(totalFileCount);
135         }
136     } else {
137         QString totalSizeStr = Utility::octetsToString(progress.totalSize());
138         if (progress.trustEta()) {
139             msg = tr("Syncing %1 (%2 left)")
140                     .arg(totalSizeStr, Utility::durationToDescriptiveString2(progress.totalProgress().estimatedEta));
141         } else {
142             msg = tr("Syncing %1")
143                     .arg(totalSizeStr);
144         }
145     }
146     updateStatusText(msg);
147 
148     if (!progress._lastCompletedItem.isEmpty()
149             && shouldShowInRecentsMenu(progress._lastCompletedItem)) {
150         GMenuItem* item;
151         g_menu_remove_all (G_MENU(_recentMenu));
152         if(!_recentlyChanged.isEmpty()) {
153             QList<QPair<QString, QString>>::iterator i;
154             for (i = _recentlyChanged.begin(); i != _recentlyChanged.end(); i++) {
155                 QString label = i->first;
156                 QString fullPath = i->second;
157                 item = menu_item_new(label, "cloudprovider.showfile");
158                 g_menu_item_set_action_and_target_value(item, "cloudprovider.showfile", g_variant_new_string(fullPath.toUtf8().data()));
159                 g_menu_append_item(_recentMenu, item);
160                 g_clear_object (&item);
161             }
162         } else {
163             item = menu_item_new(tr("No recently changed files"), nullptr);
164             g_menu_append_item(_recentMenu, item);
165             g_clear_object (&item);
166         }
167     }
168 }
169 
updateStatusText(QString statusText)170 void CloudProviderWrapper::updateStatusText(QString statusText)
171 {
172     QString status = QString("%1 - %2").arg(_folder->accountState()->stateString(_folder->accountState()->state()), statusText);
173     cloud_providers_account_exporter_set_status_details(_cloudProviderAccount, status.toUtf8().data());
174 }
175 
updatePauseStatus()176 void CloudProviderWrapper::updatePauseStatus()
177 {
178     if (_paused) {
179         updateStatusText(tr("Sync paused"));
180         cloud_providers_account_exporter_set_status (_cloudProviderAccount, CLOUD_PROVIDERS_ACCOUNT_STATUS_ERROR);
181     } else {
182         updateStatusText(tr("Syncing"));
183         cloud_providers_account_exporter_set_status (_cloudProviderAccount, CLOUD_PROVIDERS_ACCOUNT_STATUS_SYNCING);
184     }
185 }
186 
folder()187 Folder* CloudProviderWrapper::folder()
188 {
189     return _folder;
190 }
191 
slotSyncStarted()192 void CloudProviderWrapper::slotSyncStarted()
193 {
194     cloud_providers_account_exporter_set_status(_cloudProviderAccount, CLOUD_PROVIDERS_ACCOUNT_STATUS_SYNCING);
195 }
196 
slotSyncFinished(const SyncResult & result)197 void CloudProviderWrapper::slotSyncFinished(const SyncResult &result)
198 {
199     if (result.status() == result.Success || result.status() == result.Problem)
200     {
201         cloud_providers_account_exporter_set_status(_cloudProviderAccount, CLOUD_PROVIDERS_ACCOUNT_STATUS_IDLE);
202         updateStatusText(result.statusString());
203         return;
204     }
205     cloud_providers_account_exporter_set_status(_cloudProviderAccount, CLOUD_PROVIDERS_ACCOUNT_STATUS_ERROR);
206     updateStatusText(result.statusString());
207 }
208 
getMenuModel()209 GMenuModel* CloudProviderWrapper::getMenuModel() {
210 
211     GMenu* section;
212     GMenuItem* item;
213     QString item_label;
214 
215     _mainMenu = g_menu_new();
216 
217     section = g_menu_new();
218     item = menu_item_new(tr("Open website"), "cloudprovider.openwebsite");
219     g_menu_append_item(section, item);
220     g_clear_object (&item);
221     g_menu_append_section(_mainMenu, nullptr, G_MENU_MODEL(section));
222     g_clear_object (&section);
223 
224     _recentMenu = g_menu_new();
225     item = menu_item_new(tr("No recently changed files"), nullptr);
226     g_menu_append_item(_recentMenu, item);
227     g_clear_object (&item);
228 
229     section = g_menu_new();
230     item = menu_item_new_submenu(tr("Recently changed"), G_MENU_MODEL(_recentMenu));
231     g_menu_append_item(section, item);
232     g_clear_object (&item);
233     g_menu_append_section(_mainMenu, nullptr, G_MENU_MODEL(section));
234     g_clear_object (&section);
235 
236     section = g_menu_new();
237     item = menu_item_new(tr("Pause synchronization"), "cloudprovider.pause");
238     g_menu_append_item(section, item);
239     g_clear_object (&item);
240     g_menu_append_section(_mainMenu, nullptr, G_MENU_MODEL(section));
241     g_clear_object (&section);
242 
243     section = g_menu_new();
244     item = menu_item_new(tr("Help"), "cloudprovider.openhelp");
245     g_menu_append_item(section, item);
246     g_clear_object (&item);
247     item = menu_item_new(tr("Settings"), "cloudprovider.opensettings");
248     g_menu_append_item(section, item);
249     g_clear_object (&item);
250     item = menu_item_new(tr("Log out"), "cloudprovider.logout");
251     g_menu_append_item(section, item);
252     g_clear_object (&item);
253     item = menu_item_new(tr("Quit sync client"), "cloudprovider.quit");
254     g_menu_append_item(section, item);
255     g_clear_object (&item);
256     g_menu_append_section(_mainMenu, nullptr, G_MENU_MODEL(section));
257     g_clear_object (&section);
258 
259     return G_MENU_MODEL(_mainMenu);
260 }
261 
262 static void
activate_action_open(GSimpleAction * action,GVariant * parameter,gpointer user_data)263 activate_action_open (GSimpleAction *action, GVariant *parameter, gpointer user_data)
264 {
265     Q_UNUSED(parameter);
266     const gchar *name = g_action_get_name(G_ACTION(action));
267     auto *self = static_cast<CloudProviderWrapper*>(user_data);
268     auto *gui = dynamic_cast<ownCloudGui*>(self->parent()->parent());
269 
270     if(g_str_equal(name, "openhelp")) {
271         gui->slotHelp();
272     }
273 
274     if(g_str_equal(name, "opensettings")) {
275         gui->slotShowSettings();
276     }
277 
278     if(g_str_equal(name, "openwebsite")) {
279         QDesktopServices::openUrl(self->folder()->accountState()->account()->url());
280     }
281 
282     if(g_str_equal(name, "openfolder")) {
283         showInFileManager(self->folder()->cleanPath());
284     }
285 
286     if(g_str_equal(name, "showfile")) {
287         const gchar *path = g_variant_get_string(parameter, nullptr);
288         g_print("showfile => %s\n", path);
289         showInFileManager(QString(path));
290     }
291 
292     if(g_str_equal(name, "logout")) {
293         self->folder()->accountState()->signOutByUi();
294     }
295 
296     if(g_str_equal(name, "quit")) {
297         qApp->quit();
298     }
299 }
300 
301 static void
activate_action_openrecentfile(GSimpleAction * action,GVariant * parameter,gpointer user_data)302 activate_action_openrecentfile (GSimpleAction *action, GVariant *parameter, gpointer user_data)
303 {
304     Q_UNUSED(action);
305     Q_UNUSED(parameter);
306     auto *self = static_cast<CloudProviderWrapper*>(user_data);
307     QDesktopServices::openUrl(self->folder()->accountState()->account()->url());
308 }
309 
310 static void
activate_action_pause(GSimpleAction * action,GVariant * parameter,gpointer user_data)311 activate_action_pause (GSimpleAction *action,
312                        GVariant      *parameter,
313                        gpointer       user_data)
314 {
315     Q_UNUSED(parameter);
316     auto *self = static_cast<CloudProviderWrapper*>(user_data);
317     GVariant *old_state, *new_state;
318 
319     old_state = g_action_get_state (G_ACTION (action));
320     new_state = g_variant_new_boolean (!(bool)g_variant_get_boolean (old_state));
321     self->folder()->setSyncPaused((bool)g_variant_get_boolean(new_state));
322     g_simple_action_set_state (action, new_state);
323     g_variant_unref (old_state);
324 }
325 
326 static GActionEntry actions[] = {
327     { "openwebsite",  activate_action_open, nullptr, nullptr, nullptr, {0,0,0}},
328     { "quit",  activate_action_open, nullptr, nullptr, nullptr, {0,0,0}},
329     { "logout",  activate_action_open, nullptr, nullptr, nullptr, {0,0,0}},
330     { "openfolder",  activate_action_open, nullptr, nullptr, nullptr, {0,0,0}},
331     { "showfile",  activate_action_open, "s", nullptr, nullptr, {0,0,0}},
332     { "openhelp",  activate_action_open, nullptr, nullptr, nullptr, {0,0,0}},
333     { "opensettings",  activate_action_open, nullptr, nullptr, nullptr, {0,0,0}},
334     { "openrecentfile",  activate_action_openrecentfile, "s", nullptr, nullptr, {0,0,0}},
335     { "pause",  activate_action_pause, nullptr, "false", nullptr, {0,0,0}}
336 };
337 
getActionGroup()338 GActionGroup* CloudProviderWrapper::getActionGroup()
339 {
340     g_clear_object (&actionGroup);
341     actionGroup = g_simple_action_group_new ();
342     g_action_map_add_action_entries (G_ACTION_MAP (actionGroup), actions, G_N_ELEMENTS (actions), this);
343     bool state = _folder->syncPaused();
344     GAction *pause = g_action_map_lookup_action(G_ACTION_MAP(actionGroup), "pause");
345     g_simple_action_set_state(G_SIMPLE_ACTION(pause), g_variant_new_boolean(state));
346     return G_ACTION_GROUP (g_object_ref (actionGroup));
347 }
348 
slotSyncPausedChanged(Folder * folder,bool state)349 void CloudProviderWrapper::slotSyncPausedChanged(Folder *folder, bool state)
350 {
351     Q_UNUSED(folder);
352     _paused = state;
353     GAction *pause = g_action_map_lookup_action(G_ACTION_MAP(actionGroup), "pause");
354     g_simple_action_set_state (G_SIMPLE_ACTION(pause), g_variant_new_boolean(state));
355     updatePauseStatus();
356 }
357