No issues found
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 Library 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 Library 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 <stdio.h>
21 #include <errno.h>
22 #include <locale.h>
23
24 #include <glib.h>
25
26 #include <libtracker-sparql/tracker-sparql.h>
27
28 #define COPY_TIMEOUT_MS 100
29
30 static gint copy_rate = 1024; /* 1MByte/second */
31 static gint n_copies = 1;
32 static gchar **remaining;
33 static GFile *file;
34 static gchar *file_uri;
35 static GFile *destdir;
36 static gchar *destdir_uri;
37 static gboolean use_hidden;
38 static gboolean use_batch;
39
40 /* copy_rate*1024*COPY_TIMEOUT_MS/1000 */
41 static gint timeout_copy_rate;
42
43 static gchar *buffer;
44 static gsize buffer_size;
45
46 static GMainLoop *loop;
47 static GList *task_list;
48 static GList *task_list_li;
49
50 TrackerSparqlConnection *connection;
51
52 /* Note: don't use a GOutputStream, as that actually
53 * creates a hidden temporary file */
54
55 typedef struct {
56 GFile *destfile;
57 GFile *destfile_hidden;
58 FILE *fp;
59 gsize bytes_copied;
60 gsize bytes_remaining;
61 } CopyTask;
62
63 static GOptionEntry entries[] = {
64 { "rate", 'r', 0, G_OPTION_ARG_INT, ©_rate,
65 "Rate of copy, in KBytes per second",
66 "1024"
67 },
68 { "copies", 'c', 0, G_OPTION_ARG_INT, &n_copies,
69 "Number of copies to be done",
70 "1"
71 },
72 { "hidden", 'h', 0, G_OPTION_ARG_NONE, &use_hidden,
73 "Use a hidden temp file while copying",
74 NULL,
75 },
76 { "batch", 'b', 0, G_OPTION_ARG_NONE, &use_batch,
77 "Use a batch copy, using hidden temp files, and only rename the files when the batch is finished.",
78 NULL,
79 },
80 { G_OPTION_REMAINING, 0, 0,
81 G_OPTION_ARG_STRING_ARRAY, &remaining,
82 "file destdir",
83 "FILE DESTDIR",
84 },
85 { NULL }
86 };
87
88 static gchar **
89 query_urns_by_url (const gchar *uri)
90 {
91 GError *error = NULL;
92 TrackerSparqlCursor *cursor;
93 gchar *sparql;
94 gchar **urns;
95
96 sparql = g_strdup_printf ("SELECT ?urn WHERE { ?urn nie:url \"%s\" }",
97 uri);
98
99 /* Make a synchronous query to the store */
100 cursor = tracker_sparql_connection_query (connection,
101 sparql,
102 NULL,
103 &error);
104 if (error) {
105 /* Some error happened performing the query, not good */
106 g_error ("Couldn't query the Tracker Store: '%s'",
107 error ? error->message : "unknown error");
108 }
109
110 /* Check results... */
111 if (!cursor) {
112 urns = NULL;
113 } else {
114 gchar *urns_mixed;
115 GString *urns_string;
116 gint i = 0;
117
118 urns_string = g_string_new ("");
119 /* Iterate, synchronously, the results... */
120 while (tracker_sparql_cursor_next (cursor, NULL, &error)) {
121 g_string_append_printf (urns_string,
122 "%s%s",
123 i==0 ? "" : ";",
124 tracker_sparql_cursor_get_string (cursor, 0, NULL));
125 i++;
126 }
127
128 if (error) {
129 g_error ("Error iterating cursor: %s",
130 error->message);
131 }
132
133 urns_mixed = g_string_free (urns_string, FALSE);
134 urns = g_strsplit (urns_mixed, ";", -1);
135 g_free (urns_mixed);
136 g_object_unref (cursor);
137 }
138
139 g_free (sparql);
140
141 return urns;
142 }
143
144 static void
145 update_store (const gchar *sparql)
146 {
147 GError *error = NULL;
148
149 /* Run a synchronous update query */
150 tracker_sparql_connection_update (connection,
151 sparql,
152 G_PRIORITY_DEFAULT,
153 NULL,
154 &error);
155
156 if (error) {
157 /* Some error happened performing the query, not good */
158 g_error ("Couldn't update store for '%s': %s",
159 sparql,
160 error ? error->message : "unknown error");
161 }
162 }
163
164 static void
165 insert_element_in_store (GFile *destfile)
166 {
167 gchar *sparql;
168 gchar *uri;
169
170 uri = g_file_get_uri (destfile);
171 sparql = g_strdup_printf ("DELETE { ?file a rdfs:Resource} "
172 "WHERE { ?file nie:url \"%s\" } "
173 "INSERT { _:x a nfo:FileDataObject;"
174 " nie:url \"%s\" }",
175 uri, uri);
176
177 g_print (" Updating store with new resource '%s'\n", uri);
178
179 update_store (sparql);
180
181 g_free (uri);
182 g_free (sparql);
183 }
184
185 static void
186 replace_url_element_in_store (GFile *sourcefile, GFile *destfile)
187 {
188 gchar *sparql;
189 gchar *source_uri;
190 gchar *dest_uri;
191 gchar **urns;
192
193 source_uri = g_file_get_uri (sourcefile);
194 dest_uri = g_file_get_uri (destfile);
195
196
197 urns = query_urns_by_url (source_uri);
198 if (!urns || g_strv_length (urns) != 1) {
199 g_error ("Expected only 1 item with url '%s' at this point! (got %d)",
200 source_uri,
201 urns ? g_strv_length (urns) : 0);
202 }
203
204 sparql = g_strdup_printf ("DELETE { <%s> nie:url ?url} WHERE { <%s> nie:url ?url } "
205 "INSERT INTO <%s> { <%s> nie:url \"%s\" }",
206 urns[0], urns[0], urns[0], urns[0], dest_uri);
207
208 g_print (" Changing nie:url from '%s' to '%s'\n", source_uri, dest_uri);
209
210 update_store (sparql);
211
212 g_strfreev (urns);
213 g_free (source_uri);
214 g_free (dest_uri);
215 g_free (sparql);
216 }
217
218 static gboolean
219 context_init (gint argc,
220 gchar **argv)
221 {
222 GOptionContext *context;
223 gint n_remaining;
224 gchar *file_path;
225 GError *error = NULL;
226
227 /* Setup context */
228 context = g_option_context_new ("- Simulate MTP daemon");
229 g_option_context_add_main_entries (context, entries, NULL);
230 g_option_context_parse (context, &argc, &argv, NULL);
231 g_option_context_free (context);
232
233 /* Check input arguments */
234 n_remaining = remaining ? g_strv_length (remaining) : 0;
235 if (n_remaining != 2) {
236 g_printerr ("You must provide FILE and DESTDIR\n");
237 return FALSE;
238 }
239
240 /* Get and check input file */
241 file = g_file_new_for_commandline_arg (remaining[0]);
242 file_uri = g_file_get_uri (file);
243 file_path = g_file_get_path (file);
244 if (g_file_query_file_type (file,
245 G_FILE_QUERY_INFO_NONE,
246 NULL) != G_FILE_TYPE_REGULAR) {
247 g_printerr ("File '%s' is not a valid regular file\n",
248 file_uri);
249 return FALSE;
250 }
251
252 /* Get destination directory */
253 destdir = g_file_new_for_commandline_arg (remaining[1]);
254 destdir_uri = g_file_get_uri (destdir);
255 if (g_file_query_file_type (destdir,
256 G_FILE_QUERY_INFO_NONE,
257 NULL) != G_FILE_TYPE_DIRECTORY) {
258 g_printerr ("Destination path '%s' is not a valid directory\n",
259 destdir_uri);
260 return FALSE;
261 }
262
263 /* Check n_copies */
264 if (n_copies == 0) {
265 g_printerr ("Number of copies must be greater than 0\n");
266 return FALSE;
267 }
268
269 /* Check rate */
270 if (copy_rate == 0) {
271 g_printerr ("Copy rate must be greater than 0\n");
272 return FALSE;
273 }
274
275 /* copy_rate*1024*COPY_TIMEOUT_MS/1000 */
276 timeout_copy_rate = copy_rate * 1024.0 * COPY_TIMEOUT_MS / 1000.0;
277
278 /* Read all input file contents */
279 if (!g_file_get_contents (file_path,
280 &buffer,
281 &buffer_size,
282 &error)) {
283 g_error ("Couldn't load file '%s' contents: %s",
284 file_uri,
285 error ? error->message : "unknown error");
286 }
287
288 /* Get connection */
289 connection = tracker_sparql_connection_get (NULL, &error);
290 if (!connection) {
291 /* Some error happened performing the query, not good */
292 g_error ("Couldn't get sparql connection: %s",
293 error ? error->message : "unknown error");
294 }
295
296 g_print ("\
297 Simulating MTP daemon with:\n\
298 * File: %s (%" G_GSIZE_FORMAT " bytes)\n\
299 * Destdir: %s\n\
300 * Copies: %d\n\
301 * Rate: %d KBytes/s (%d bytes every %d ms)\n\
302 * Mode: %s\n",
303 file_uri,
304 buffer_size,
305 destdir_uri,
306 n_copies,
307 copy_rate,
308 timeout_copy_rate,
309 COPY_TIMEOUT_MS,
310 use_batch ? "Hidden & Batch" : use_hidden ? "Hidden" : "Normal");
311
312 return TRUE;
313 }
314
315 static void
316 context_deinit (void)
317 {
318 g_object_unref (file);
319 g_free (file_uri);
320 g_object_unref (destdir);
321 g_free (destdir_uri);
322 g_free (buffer);
323 g_object_unref (connection);
324 }
325
326 static gboolean
327 task_run_cb (gpointer data)
328 {
329 CopyTask *current;
330 gsize n_write;
331
332 /* Get current task */
333 current = task_list_li ? task_list_li->data : NULL;
334
335 /* Stop looping? */
336 if (!current) {
337 g_print ("\n\nNo more tasks to run, finishing...\n");
338 g_main_loop_quit (loop);
339 return FALSE;
340 }
341
342 /* If we just started copying it... */
343 if (!current->fp) {
344 gchar *destfile_path;
345
346 g_print ("Running new copy task...\n");
347
348 destfile_path = (use_hidden || use_batch ?
349 g_file_get_path (current->destfile_hidden) :
350 g_file_get_path (current->destfile));
351
352 /* Get file pointer */
353 current->fp = fopen (destfile_path, "w");
354 if (!current->fp)
355 {
356 g_error ("Couldn't get file pointer: %s",
357 g_strerror (errno));
358 }
359
360 /* Create new item in the store right away */
361 insert_element_in_store (use_hidden || use_batch ?
362 current->destfile_hidden :
363 current->destfile);
364 g_free (destfile_path);
365 }
366
367 /* Copy bytes */
368 n_write = MIN (current->bytes_remaining, timeout_copy_rate);
369 if (fwrite (&buffer[current->bytes_copied],
370 1,
371 n_write,
372 current->fp) != n_write) {
373 g_error ("Couldn't write in output file: %s",
374 g_strerror (errno));
375 }
376
377 current->bytes_remaining -= n_write;
378 current->bytes_copied += n_write;
379
380 /* Finished with this task? */
381 if (current->bytes_remaining == 0) {
382 fclose (current->fp);
383 current->fp = NULL;
384
385 if (use_hidden && !use_batch) {
386 GError *error = NULL;
387
388 /* Change nie:url in the store */
389 replace_url_element_in_store (current->destfile_hidden,
390 current->destfile);
391
392 /* Copying finished, now MOVE to the final path */
393 if (!g_file_move (current->destfile_hidden,
394 current->destfile,
395 G_FILE_COPY_OVERWRITE,
396 NULL,
397 NULL,
398 NULL,
399 &error)) {
400 g_error ("Couldn't copy file to the final destination: %s",
401 error ? error->message : "unknown error");
402 }
403 }
404
405 /* Setup next task */
406 task_list_li = g_list_next (task_list_li);
407
408 /* If this is the LAST task the one we just processed, perform the
409 * batch renaming if required. */
410 if (!task_list_li && use_batch) {
411 for (task_list_li = task_list;
412 task_list_li;
413 task_list_li = g_list_next (task_list_li)) {
414 GError *error = NULL;
415
416 current = task_list_li->data;
417 /* Change nie:url in the store */
418 replace_url_element_in_store (current->destfile_hidden,
419 current->destfile);
420
421 /* Copying finished, now MOVE to the final path */
422 if (!g_file_move (current->destfile_hidden,
423 current->destfile,
424 G_FILE_COPY_OVERWRITE,
425 NULL,
426 NULL,
427 NULL,
428 &error)) {
429 g_error ("Couldn't copy file to the final destination (batch): %s",
430 error ? error->message : "unknown error");
431 }
432 }
433 }
434 }
435
436 return TRUE;
437 }
438
439 static void
440 setup_tasks (void)
441 {
442 gint i;
443 gchar *input_file_basename;
444
445 input_file_basename = g_file_get_basename (file);
446
447 for (i=n_copies-1; i>=0; i--) {
448 CopyTask *task;
449 gchar *basename;
450
451 basename = g_strdup_printf ("file-copy-%d-%s",
452 i,
453 input_file_basename);
454
455 task = g_new0 (CopyTask, 1);
456 task->destfile = g_file_get_child (destdir, basename);
457 task->bytes_remaining = buffer_size;
458
459 if (use_hidden || use_batch) {
460 gchar *basename_hidden;
461
462 basename_hidden = g_strdup_printf (".mtp-dummy.file-copy-%d-%s",
463 i,
464 input_file_basename);
465 task->destfile_hidden = g_file_get_child (destdir, basename_hidden);
466 g_free (basename_hidden);
467 }
468
469 task_list = g_list_prepend (task_list, task);
470
471 g_free (basename);
472 }
473
474 /* Setup first task */
475 task_list_li = task_list;
476
477 /* Timeout every N milliseconds */
478 g_timeout_add (COPY_TIMEOUT_MS,
479 task_run_cb,
480 NULL);
481 }
482
483 static void
484 check_duplicates_for_uri (const gchar *uri)
485 {
486 gchar **urns;
487
488 urns = query_urns_by_url (uri);
489
490 /* Check results... */
491 if (!urns) {
492 g_print (" For '%s' found 0 results!\n", uri);
493 } else {
494 gint i;
495
496 g_print (" A total of '%d results where found for '%s':\n",
497 g_strv_length (urns), uri);
498 for (i=0; urns[i]; i++) {
499 g_print (" [%d]: %s\n", i, urns[i]);
500 }
501 g_strfreev (urns);
502 }
503 }
504
505 static void
506 check_duplicates (void)
507 {
508 g_print ("\nChecking duplicates...\n");
509
510 task_list_li = task_list;
511
512 while (task_list_li) {
513 CopyTask *current;
514 gchar *uri;
515
516 current = task_list_li->data;
517 uri = g_file_get_uri (current->destfile);
518
519 check_duplicates_for_uri (uri);
520
521 g_free (uri);
522 g_object_unref (current->destfile);
523 g_free (current);
524 task_list_li = g_list_next (task_list_li);;
525 }
526
527 g_list_free (task_list);
528 }
529
530 int main (int argc, char **argv)
531 {
532 /* Initialize locale support! */
533 setlocale (LC_ALL, "");
534
535 /* Initialize context */
536 if (!context_init (argc, argv)) {
537 g_printerr ("Couldn't setup context, exiting.");
538 return -1;
539 }
540
541 /* Setup tasks */
542 setup_tasks ();
543
544 /* Run */
545 g_print ("\nStarting...\n\n");
546 loop = g_main_loop_new (NULL, FALSE);
547 g_main_loop_run (loop);
548 g_main_loop_unref (loop);
549
550 /* Check for duplicates and cleanup copied files */
551 check_duplicates ();
552
553 context_deinit ();
554 return 0;
555 }