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