1 /*
2  * Copyright (C) 2010, Nokia <ivan.frade@nokia.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA  02110-1301, USA.
18  */
19 
20 #include "config-miners.h"
21 
22 #include <libtracker-miners-common/tracker-dbus.h>
23 #include <libtracker-sparql/tracker-sparql.h>
24 
25 #include "tracker-writeback-dispatcher.h"
26 #include "tracker-miner-files.h"
27 
28 #define TRACKER_WRITEBACK_SERVICE   "org.freedesktop.Tracker1.Writeback"
29 #define TRACKER_WRITEBACK_PATH      "/org/freedesktop/Tracker1/Writeback"
30 #define TRACKER_WRITEBACK_INTERFACE "org.freedesktop.Tracker1.Writeback"
31 
32 typedef struct {
33 	GFile *file;
34 	TrackerMinerFiles *fs;
35 	GPtrArray *results;
36 	GStrv rdf_types;
37 	TrackerWritebackDispatcher *self; /* weak */
38 	GCancellable *cancellable;
39 	guint cancel_id;
40 	guint retry_timeout;
41 	guint retries;
42 } WritebackFileData;
43 
44 typedef struct {
45 	TrackerMinerFiles *files_miner;
46 	GDBusConnection *d_connection;
47 	TrackerSparqlConnection *connection;
48 } TrackerWritebackDispatcherPrivate;
49 
50 enum {
51 	PROP_0,
52 	PROP_FILES_MINER
53 };
54 
55 #define TRACKER_WRITEBACK_DISPATCHER_GET_PRIVATE(o) (tracker_writeback_dispatcher_get_instance_private (TRACKER_WRITEBACK_DISPATCHER (o)))
56 
57 static void     writeback_dispatcher_set_property    (GObject              *object,
58                                                       guint                 param_id,
59                                                       const GValue         *value,
60                                                       GParamSpec           *pspec);
61 static void     writeback_dispatcher_get_property    (GObject              *object,
62                                                       guint                 param_id,
63                                                       GValue               *value,
64                                                       GParamSpec           *pspec);
65 static void     writeback_dispatcher_finalize        (GObject              *object);
66 static gboolean writeback_dispatcher_initable_init   (GInitable            *initable,
67                                                       GCancellable         *cancellable,
68                                                       GError              **error);
69 static void     writeback_dispatcher_writeback_file  (TrackerMinerFS       *fs,
70                                                       GFile                *file,
71                                                       GStrv                 rdf_types,
72                                                       GPtrArray            *results,
73                                                       GCancellable         *cancellable,
74                                                       gpointer              user_data);
75 static void     self_weak_notify                     (gpointer              data,
76                                                       GObject              *where_the_object_was);
77 
78 static void
writeback_dispatcher_initable_iface_init(GInitableIface * iface)79 writeback_dispatcher_initable_iface_init (GInitableIface *iface)
80 {
81 	iface->init = writeback_dispatcher_initable_init;
82 }
83 
84 G_DEFINE_TYPE_WITH_CODE (TrackerWritebackDispatcher, tracker_writeback_dispatcher, G_TYPE_OBJECT,
85                          G_ADD_PRIVATE (TrackerWritebackDispatcher)
86                          G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
87                                                 writeback_dispatcher_initable_iface_init));
88 
89 static void
tracker_writeback_dispatcher_class_init(TrackerWritebackDispatcherClass * klass)90 tracker_writeback_dispatcher_class_init (TrackerWritebackDispatcherClass *klass)
91 {
92 	GObjectClass *object_class;
93 
94 	object_class = G_OBJECT_CLASS (klass);
95 
96 	object_class->finalize = writeback_dispatcher_finalize;
97 	object_class->set_property = writeback_dispatcher_set_property;
98 	object_class->get_property = writeback_dispatcher_get_property;
99 
100 	g_object_class_install_property (object_class,
101 	                                 PROP_FILES_MINER,
102 	                                 g_param_spec_object ("files_miner",
103 	                                                      "files_miner",
104 	                                                      "The FS Miner",
105 	                                                      TRACKER_TYPE_MINER_FILES,
106 	                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
107 }
108 
109 static void
writeback_dispatcher_set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)110 writeback_dispatcher_set_property (GObject      *object,
111                                    guint         param_id,
112                                    const GValue *value,
113                                    GParamSpec   *pspec)
114 {
115 	TrackerWritebackDispatcherPrivate *priv;
116 
117 	priv = TRACKER_WRITEBACK_DISPATCHER_GET_PRIVATE (object);
118 
119 	switch (param_id) {
120 	case PROP_FILES_MINER:
121 		priv->files_miner = g_value_dup_object (value);
122 		break;
123 	default:
124 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
125 		break;
126 	}
127 }
128 
129 
130 static void
writeback_dispatcher_get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)131 writeback_dispatcher_get_property (GObject    *object,
132                                    guint       param_id,
133                                    GValue     *value,
134                                    GParamSpec *pspec)
135 {
136 	TrackerWritebackDispatcherPrivate *priv;
137 
138 	priv = TRACKER_WRITEBACK_DISPATCHER_GET_PRIVATE (object);
139 
140 	switch (param_id) {
141 	case PROP_FILES_MINER:
142 		g_value_set_object (value, priv->files_miner);
143 		break;
144 	default:
145 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
146 		break;
147 	}
148 }
149 
150 static void
writeback_dispatcher_finalize(GObject * object)151 writeback_dispatcher_finalize (GObject *object)
152 {
153 	TrackerWritebackDispatcherPrivate *priv = TRACKER_WRITEBACK_DISPATCHER_GET_PRIVATE (object);
154 
155 	if (priv->connection) {
156 		g_object_unref (priv->connection);
157 	}
158 
159 	if (priv->d_connection) {
160 		g_object_unref (priv->d_connection);
161 	}
162 
163 	if (priv->files_miner) {
164 		g_object_unref (priv->files_miner);
165 	}
166 }
167 
168 static void
tracker_writeback_dispatcher_init(TrackerWritebackDispatcher * object)169 tracker_writeback_dispatcher_init (TrackerWritebackDispatcher *object)
170 {
171 }
172 
173 static gboolean
writeback_dispatcher_initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)174 writeback_dispatcher_initable_init (GInitable    *initable,
175                                     GCancellable *cancellable,
176                                     GError       **error)
177 {
178 	TrackerWritebackDispatcherPrivate *priv;
179 	GError *internal_error = NULL;
180 
181 	priv = TRACKER_WRITEBACK_DISPATCHER_GET_PRIVATE (initable);
182 
183 	priv->connection = tracker_sparql_connection_get (NULL, &internal_error);
184 
185 	if (internal_error) {
186 		g_propagate_error (error, internal_error);
187 		return FALSE;
188 	}
189 
190 	priv->d_connection = g_bus_get_sync (TRACKER_IPC_BUS, NULL, &internal_error);
191 
192 	if (internal_error) {
193 		g_propagate_error (error, internal_error);
194 		return FALSE;
195 	}
196 
197 	g_signal_connect_object (priv->files_miner,
198 	                         "writeback",
199 	                         G_CALLBACK (writeback_dispatcher_writeback_file),
200 	                         initable,
201 	                         G_CONNECT_AFTER);
202 
203 	return TRUE;
204 }
205 
206 TrackerWritebackDispatcher *
tracker_writeback_dispatcher_new(TrackerMinerFiles * miner_files,GError ** error)207 tracker_writeback_dispatcher_new (TrackerMinerFiles  *miner_files,
208                                   GError            **error)
209 {
210 	GObject *miner;
211 	GError *internal_error = NULL;
212 
213 	miner =  g_initable_new (TRACKER_TYPE_WRITEBACK_DISPATCHER,
214 	                         NULL,
215 	                         &internal_error,
216 	                         "files-miner", miner_files,
217 	                         NULL);
218 
219 	if (internal_error) {
220 		g_propagate_error (error, internal_error);
221 		return NULL;
222 	}
223 
224 	return (TrackerWritebackDispatcher *) miner;
225 }
226 
227 static void
writeback_file_data_free(WritebackFileData * data)228 writeback_file_data_free (WritebackFileData *data)
229 {
230 	if (data->self) {
231 		g_object_weak_unref (G_OBJECT (data->self), self_weak_notify, data);
232 	}
233 
234 	g_object_unref (data->fs);
235 	g_object_unref (data->file);
236 	g_strfreev (data->rdf_types);
237 	g_ptr_array_unref (data->results);
238 	g_cancellable_disconnect (data->cancellable, data->cancel_id);
239 	g_object_unref (data->cancellable);
240 	g_free (data);
241 }
242 
243 static void
self_weak_notify(gpointer data,GObject * where_the_object_was)244 self_weak_notify (gpointer data, GObject *where_the_object_was)
245 {
246 	WritebackFileData *udata = data;
247 
248 	/* Shut down while retrying writeback */
249 	g_debug ("Shutdown occurred while retrying write-back (after unmount), not retrying anymore");
250 
251 	if (udata->retry_timeout != 0) {
252 		g_source_remove (udata->retry_timeout);
253 	}
254 
255 	udata->self = NULL;
256 	writeback_file_data_free (udata);
257 }
258 
259 static gboolean
retry_idle(gpointer user_data)260 retry_idle (gpointer user_data)
261 {
262 	WritebackFileData *data = user_data;
263 
264 	tracker_miner_files_writeback_file (data->fs,
265 	                                    data->file,
266 	                                    data->rdf_types,
267 	                                    data->results);
268 
269 	writeback_file_data_free (data);
270 
271 	return FALSE;
272 }
273 
274 static void
writeback_file_finished(GObject * source_object,GAsyncResult * res,gpointer user_data)275 writeback_file_finished  (GObject      *source_object,
276                           GAsyncResult *res,
277                           gpointer      user_data)
278 {
279 	WritebackFileData *data = user_data;
280 	GError *error = NULL;
281 
282 	g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
283 	                               res, &error);
284 
285 	if (error && error->code == G_DBUS_ERROR_NO_REPLY && data->retries < 5) {
286 		/* This happens in case of exit() of the tracker-writeback binary, which
287 		 * happens on unmount of the FS event, for example */
288 
289 		g_debug ("Retrying write-back after unmount (timeout in 5 seconds)");
290 		tracker_miner_files_writeback_notify (data->fs, data->file, NULL);
291 
292 		data->retry_timeout = g_timeout_add_seconds (5, retry_idle, data);
293 		data->retries++;
294 
295 	} else {
296 		tracker_miner_files_writeback_notify (data->fs, data->file, error);
297 		writeback_file_data_free (data);
298 	}
299 }
300 
301 static void
writeback_cancel_remote_operation(GCancellable * cancellable,WritebackFileData * data)302 writeback_cancel_remote_operation (GCancellable      *cancellable,
303                                    WritebackFileData *data)
304 {
305 	TrackerWritebackDispatcherPrivate *priv;
306 	GDBusMessage *message;
307         gchar *uris[2];
308 
309 	priv = TRACKER_WRITEBACK_DISPATCHER_GET_PRIVATE (data->self);
310 
311 	uris[0] = g_file_get_uri (data->file);
312 	uris[1] = NULL;
313 
314 	message = g_dbus_message_new_method_call (TRACKER_WRITEBACK_SERVICE,
315 	                                          TRACKER_WRITEBACK_PATH,
316 	                                          TRACKER_WRITEBACK_INTERFACE,
317 	                                          "CancelTasks");
318 
319 	g_dbus_message_set_body (message, g_variant_new ("(^as)", uris));
320 	g_dbus_connection_send_message (priv->d_connection, message,
321 	                                G_DBUS_SEND_MESSAGE_FLAGS_NONE,
322 	                                NULL, NULL);
323 	g_free (uris[0]);
324 }
325 
326 static void
writeback_dispatcher_writeback_file(TrackerMinerFS * fs,GFile * file,GStrv rdf_types,GPtrArray * results,GCancellable * cancellable,gpointer user_data)327 writeback_dispatcher_writeback_file (TrackerMinerFS *fs,
328                                      GFile          *file,
329                                      GStrv           rdf_types,
330                                      GPtrArray      *results,
331                                      GCancellable   *cancellable,
332                                      gpointer        user_data)
333 {
334 	TrackerWritebackDispatcher *self = user_data;
335 	TrackerWritebackDispatcherPrivate *priv;
336 	gchar *uri;
337 	guint i;
338 	GVariantBuilder builder;
339 	WritebackFileData *data = g_new (WritebackFileData, 1);
340 
341 	priv = TRACKER_WRITEBACK_DISPATCHER_GET_PRIVATE (self);
342 
343 	uri = g_file_get_uri (file);
344 	g_debug ("Performing write-back for '%s'", uri);
345 
346 	g_variant_builder_init (&builder, G_VARIANT_TYPE ("(sasaas)"));
347 
348 	g_variant_builder_add (&builder, "s", uri);
349 
350 	g_variant_builder_open (&builder, G_VARIANT_TYPE ("as"));
351 	for (i = 0; rdf_types[i] != NULL; i++) {
352 		g_variant_builder_add (&builder, "s", rdf_types[i]);
353 	}
354 	g_variant_builder_close (&builder);
355 
356 	g_variant_builder_open (&builder, G_VARIANT_TYPE ("aas"));
357 
358 	for (i = 0; i< results->len; i++) {
359 		GStrv row = g_ptr_array_index (results, i);
360 		guint y;
361 
362 		g_variant_builder_open (&builder, G_VARIANT_TYPE ("as"));
363 		for (y = 0; row[y] != NULL; y++) {
364 			g_variant_builder_add (&builder, "s", row[y]);
365 		}
366 
367 		g_variant_builder_close (&builder);
368 	}
369 
370 	g_variant_builder_close (&builder);
371 
372 	data->retries = 0;
373 	data->retry_timeout = 0;
374 	data->self = self;
375 	g_object_weak_ref (G_OBJECT (data->self), self_weak_notify, data);
376 	data->fs = TRACKER_MINER_FILES (g_object_ref (fs));
377 	data->file = g_object_ref (file);
378 	data->results = g_ptr_array_ref (results);
379 	data->rdf_types = g_strdupv (rdf_types);
380 	data->cancellable = g_object_ref (cancellable);
381 	data->cancel_id = g_cancellable_connect (data->cancellable,
382 	                                         G_CALLBACK (writeback_cancel_remote_operation),
383 	                                         data, NULL);
384 
385 	g_dbus_connection_call (priv->d_connection,
386 	                        TRACKER_WRITEBACK_SERVICE,
387 	                        TRACKER_WRITEBACK_PATH,
388 	                        TRACKER_WRITEBACK_INTERFACE,
389 	                        "PerformWriteback",
390 	                        g_variant_builder_end (&builder),
391 	                        NULL,
392 	                        G_DBUS_CALL_FLAGS_NONE,
393 	                        -1,
394 	                        cancellable,
395 	                        (GAsyncReadyCallback) writeback_file_finished,
396 	                        data);
397 
398 	g_free (uri);
399 }
400