1 /*
2  *   SPDX-FileCopyrightText: 2017 Jan Grulich <jgrulich@redhat.com>
3  *
4  *   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5  */
6 
7 #include "FlatpakTransactionThread.h"
8 #include "FlatpakResource.h"
9 
10 #include <KLocalizedString>
11 #include <QDebug>
12 
13 static int FLATPAK_CLI_UPDATE_FREQUENCY = 150;
14 
add_new_remote_cb(FlatpakTransaction *,gint,gchar * from_id,gchar * suggested_remote_name,gchar * url,gpointer user_data)15 gboolean add_new_remote_cb(FlatpakTransaction * /*object*/, gint /*reason*/, gchar *from_id, gchar *suggested_remote_name, gchar *url, gpointer user_data)
16 {
17     FlatpakTransactionThread *obj = (FlatpakTransactionThread *)user_data;
18 
19     // TODO ask instead
20     Q_EMIT obj->passiveMessage(
21         i18n("Adding remote '%1' in %2 from %3", QString::fromUtf8(suggested_remote_name), QString::fromUtf8(url), QString::fromUtf8(from_id)));
22     return true;
23 }
24 
progress_changed_cb(FlatpakTransactionProgress * progress,gpointer user_data)25 static void progress_changed_cb(FlatpakTransactionProgress *progress, gpointer user_data)
26 {
27     FlatpakTransactionThread *obj = (FlatpakTransactionThread *)user_data;
28 
29     obj->setProgress(qMin(99, flatpak_transaction_progress_get_progress(progress)));
30 
31 #ifdef FLATPAK_VERBOSE_PROGRESS
32     guint64 start_time = flatpak_transaction_progress_get_start_time(progress);
33     guint64 elapsed_time = (g_get_monotonic_time() - start_time) / G_USEC_PER_SEC;
34     if (elapsed_time > 0) {
35         guint64 transferred = flatpak_transaction_progress_get_bytes_transferred(progress);
36         obj->setSpeed(transferred / elapsed_time);
37     }
38 #endif
39 }
40 
new_operation_cb(FlatpakTransaction *,FlatpakTransactionOperation *,FlatpakTransactionProgress * progress,gpointer user_data)41 void new_operation_cb(FlatpakTransaction * /*object*/, FlatpakTransactionOperation * /*operation*/, FlatpakTransactionProgress *progress, gpointer user_data)
42 {
43     FlatpakTransactionThread *obj = (FlatpakTransactionThread *)user_data;
44 
45     g_signal_connect(progress, "changed", G_CALLBACK(progress_changed_cb), obj);
46     flatpak_transaction_progress_set_update_frequency(progress, FLATPAK_CLI_UPDATE_FREQUENCY);
47 }
48 
operation_error_cb(FlatpakTransaction *,FlatpakTransactionOperation *,GError * error,gint,gpointer user_data)49 void operation_error_cb(FlatpakTransaction * /*object*/, FlatpakTransactionOperation * /*operation*/, GError *error, gint /*details*/, gpointer user_data)
50 {
51     FlatpakTransactionThread *obj = (FlatpakTransactionThread *)user_data;
52     obj->addErrorMessage(QString::fromUtf8(error->message));
53 }
54 
FlatpakTransactionThread(FlatpakResource * app,Transaction::Role role)55 FlatpakTransactionThread::FlatpakTransactionThread(FlatpakResource *app, Transaction::Role role)
56     : QThread()
57     , m_result(false)
58     , m_app(app)
59     , m_role(role)
60 {
61     m_cancellable = g_cancellable_new();
62 
63     g_autoptr(GError) localError = nullptr;
64     m_transaction = flatpak_transaction_new_for_installation(app->installation(), m_cancellable, &localError);
65     if (localError) {
66         addErrorMessage(QString::fromUtf8(localError->message));
67         qWarning() << "Failed to create transaction" << m_errorMessage;
68     } else {
69         g_signal_connect(m_transaction, "add-new-remote", G_CALLBACK(add_new_remote_cb), this);
70         g_signal_connect(m_transaction, "new-operation", G_CALLBACK(new_operation_cb), this);
71         g_signal_connect(m_transaction, "operation-error", G_CALLBACK(operation_error_cb), this);
72     }
73 }
74 
~FlatpakTransactionThread()75 FlatpakTransactionThread::~FlatpakTransactionThread()
76 {
77     g_object_unref(m_transaction);
78     g_object_unref(m_cancellable);
79 }
80 
cancel()81 void FlatpakTransactionThread::cancel()
82 {
83     g_cancellable_cancel(m_cancellable);
84 }
85 
run()86 void FlatpakTransactionThread::run()
87 {
88     if (!m_transaction)
89         return;
90     g_autoptr(GError) localError = nullptr;
91 
92     const QString refName = m_app->ref();
93 
94     if (m_role == Transaction::Role::InstallRole) {
95         bool correct = false;
96         if (m_app->state() == AbstractResource::Upgradeable && m_app->isInstalled()) {
97             correct = flatpak_transaction_add_update(m_transaction, refName.toUtf8().constData(), nullptr, nullptr, &localError);
98         } else if (m_app->flatpakFileType() == FlatpakResource::FileFlatpak) {
99             g_autoptr(GFile) file = g_file_new_for_path(m_app->resourceFile().toLocalFile().toUtf8().constData());
100             if (!file) {
101                 qWarning() << "Failed to install bundled application" << refName;
102                 m_result = false;
103                 return;
104             }
105             correct = flatpak_transaction_add_install_bundle(m_transaction, file, nullptr, &localError);
106         } else if (m_app->flatpakFileType() == FlatpakResource::FileFlatpakRef && m_app->resourceFile().isLocalFile()) {
107             g_autoptr(GFile) file = g_file_new_for_path(m_app->resourceFile().toLocalFile().toUtf8().constData());
108             if (!file) {
109                 qWarning() << "Failed to install flatpakref application" << refName;
110                 m_result = false;
111                 return;
112             }
113             g_autoptr(GBytes) bytes = g_file_load_bytes(file, m_cancellable, nullptr, &localError);
114             correct = flatpak_transaction_add_install_flatpakref(m_transaction, bytes, &localError);
115         } else {
116             correct = flatpak_transaction_add_install(m_transaction, //
117                                                       m_app->origin().toUtf8().constData(),
118                                                       refName.toUtf8().constData(),
119                                                       nullptr,
120                                                       &localError);
121         }
122 
123         if (!correct) {
124             m_result = false;
125             m_errorMessage = QString::fromUtf8(localError->message);
126             // We are done so we can set the progress to 100
127             setProgress(100);
128             qWarning() << "Failed to install" << m_app->flatpakFileType() << refName << ':' << m_errorMessage;
129             return;
130         }
131     } else if (m_role == Transaction::Role::RemoveRole) {
132         if (!flatpak_transaction_add_uninstall(m_transaction, refName.toUtf8().constData(), &localError)) {
133             m_result = false;
134             m_errorMessage = QString::fromUtf8(localError->message);
135             // We are done so we can set the progress to 100
136             setProgress(100);
137             qWarning() << "Failed to uninstall" << refName << ':' << m_errorMessage;
138             return;
139         }
140     }
141 
142     m_result = flatpak_transaction_run(m_transaction, m_cancellable, &localError);
143     m_cancelled = g_cancellable_is_cancelled(m_cancellable);
144     if (!m_result) {
145         m_errorMessage = QString::fromUtf8(localError->message);
146 #if defined(FLATPAK_LIST_UNUSED_REFS)
147     } else {
148         const auto installation = flatpak_transaction_get_installation(m_transaction);
149         g_autoptr(GPtrArray) refs = flatpak_installation_list_unused_refs(installation, nullptr, m_cancellable, nullptr);
150         if (refs->len > 0) {
151             g_autoptr(GError) localError = nullptr;
152             qDebug() << "found unused refs:" << refs->len;
153             auto transaction = flatpak_transaction_new_for_installation(installation, m_cancellable, &localError);
154             for (uint i = 0; i < refs->len; i++) {
155                 FlatpakRef *ref = FLATPAK_REF(g_ptr_array_index(refs, i));
156                 g_autofree gchar *strRef = flatpak_ref_format_ref(ref);
157                 qDebug() << "unused ref:" << strRef;
158                 if (!flatpak_transaction_add_uninstall(transaction, strRef, &localError)) {
159                     qDebug() << "failed to uninstall unused ref" << refName << localError->message;
160                     break;
161                 }
162             }
163             if (!flatpak_transaction_run(transaction, m_cancellable, &localError)) {
164                 qWarning() << "could not properly clean the elements" << refs->len << localError->message;
165             }
166             g_object_unref(m_transaction);
167         }
168 #endif
169     }
170     // We are done so we can set the progress to 100
171     setProgress(100);
172 }
173 
setProgress(int progress)174 void FlatpakTransactionThread::setProgress(int progress)
175 {
176     Q_ASSERT(qBound(0, progress, 100) == progress);
177     if (m_progress != progress) {
178         m_progress = progress;
179         Q_EMIT progressChanged(m_progress);
180     }
181 }
182 
setSpeed(quint64 speed)183 void FlatpakTransactionThread::setSpeed(quint64 speed)
184 {
185     if (m_speed != speed) {
186         m_speed = speed;
187         Q_EMIT speedChanged(m_speed);
188     }
189 }
190 
errorMessage() const191 QString FlatpakTransactionThread::errorMessage() const
192 {
193     return m_errorMessage;
194 }
195 
result() const196 bool FlatpakTransactionThread::result() const
197 {
198     return m_result;
199 }
200 
addErrorMessage(const QString & error)201 void FlatpakTransactionThread::addErrorMessage(const QString &error)
202 {
203     if (!m_errorMessage.isEmpty())
204         m_errorMessage.append(QLatin1Char('\n'));
205     m_errorMessage.append(error);
206 }
207