3
* Copyright (C) 2015 Christian Hergert <christian@hergert.me>
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 3 of the License, or
8
* (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19
#define G_LOG_DOMAIN "ide-unsaved-files"
22
#include <glib/gstdio.h>
25
#include "ide-context.h"
26
#include "ide-debug.h"
27
#include "ide-global.h"
28
#include "ide-internal.h"
29
#include "ide-project.h"
30
#include "ide-unsaved-file.h"
31
#include "ide-unsaved-files.h"
40
IdeUnsavedFiles *backptr;
45
GPtrArray *unsaved_files;
47
} IdeUnsavedFilesPrivate;
51
GPtrArray *unsaved_files;
52
gchar *drafts_directory;
55
G_DEFINE_TYPE_WITH_PRIVATE (IdeUnsavedFiles, ide_unsaved_files, IDE_TYPE_OBJECT)
58
get_drafts_directory (IdeContext *context)
61
const gchar *project_name;
63
project = ide_context_get_project (context);
64
project_name = ide_project_get_id (project);
66
return g_build_filename (g_get_user_data_dir (),
67
ide_get_program_name (),
74
async_state_free (gpointer data)
76
AsyncState *state = data;
80
g_free (state->drafts_directory);
81
g_ptr_array_unref (state->unsaved_files);
82
g_slice_free (AsyncState, state);
87
unsaved_file_free (gpointer data)
89
UnsavedFile *uf = data;
93
g_clear_object (&uf->file);
94
g_clear_pointer (&uf->content, g_bytes_unref);
96
if (uf->temp_path != NULL)
98
g_unlink (uf->temp_path);
99
g_clear_pointer (&uf->temp_path, g_free);
102
if (uf->temp_fd != -1)
104
g_close (uf->temp_fd, NULL);
108
g_slice_free (UnsavedFile, uf);
113
unsaved_file_copy (const UnsavedFile *uf)
117
copy = g_slice_new0 (UnsavedFile);
118
copy->file = g_object_ref (uf->file);
119
copy->content = g_bytes_ref (uf->content);
125
unsaved_file_save (UnsavedFile *uf,
132
g_assert (uf->content);
135
ret = g_file_set_contents (path,
136
g_bytes_get_data (uf->content, NULL),
137
g_bytes_get_size (uf->content),
143
hash_uri (const gchar *uri)
148
checksum = g_checksum_new (G_CHECKSUM_SHA1);
149
g_checksum_update (checksum, (guchar *)uri, strlen (uri));
150
ret = g_strdup (g_checksum_get_string (checksum));
151
g_checksum_free (checksum);
157
ide_unsaved_files_save_worker (GTask *task,
158
gpointer source_object,
160
GCancellable *cancellable)
163
AsyncState *state = task_data;
164
g_autofree gchar *manifest_path = NULL;
165
GError *error = NULL;
168
g_assert (G_IS_TASK (task));
169
g_assert (IDE_IS_UNSAVED_FILES (source_object));
172
/* ensure that the directory exists */
173
if (g_mkdir_with_parents (state->drafts_directory, 0700) != 0)
175
error = g_error_new_literal (G_IO_ERROR,
176
g_io_error_from_errno (errno),
177
"Failed to create drafts directory");
178
g_task_return_error (task, error);
182
manifest = g_string_new (NULL);
183
manifest_path = g_build_filename (state->drafts_directory,
187
for (i = 0; i < state->unsaved_files->len; i++)
189
g_autofree gchar *path = NULL;
190
g_autofree gchar *uri = NULL;
191
g_autofree gchar *hash = NULL;
194
uf = g_ptr_array_index (state->unsaved_files, i);
196
uri = g_file_get_uri (uf->file);
198
g_string_append_printf (manifest, "%s\n", uri);
200
hash = hash_uri (uri);
201
path = g_build_filename (state->drafts_directory, hash, NULL);
203
if (!unsaved_file_save (uf, path, &error))
205
g_task_return_error (task, error);
210
if (!g_file_set_contents (manifest_path,
211
manifest->str, manifest->len,
214
g_task_return_error (task, error);
218
g_task_return_boolean (task, TRUE);
221
g_string_free (manifest, TRUE);
225
async_state_new (IdeUnsavedFiles *files)
230
g_assert (IDE_IS_UNSAVED_FILES (files));
232
context = ide_object_get_context (IDE_OBJECT (files));
234
state = g_slice_new (AsyncState);
235
state->unsaved_files = g_ptr_array_new_with_free_func (unsaved_file_free);
236
state->drafts_directory = get_drafts_directory (context);
242
ide_unsaved_files_save_async (IdeUnsavedFiles *files,
243
GCancellable *cancellable,
244
GAsyncReadyCallback callback,
247
IdeUnsavedFilesPrivate *priv;
248
g_autoptr(GTask) task = NULL;
252
g_return_if_fail (IDE_IS_UNSAVED_FILES (files));
253
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
255
priv = ide_unsaved_files_get_instance_private (files);
257
state = async_state_new (files);
259
for (i = 0; i < priv->unsaved_files->len; i++)
262
UnsavedFile *uf_copy;
264
uf = g_ptr_array_index (priv->unsaved_files, i);
265
uf_copy = unsaved_file_copy (uf);
266
g_ptr_array_add (state->unsaved_files, uf_copy);
269
task = g_task_new (files, cancellable, callback, user_data);
270
g_task_set_task_data (task, state, async_state_free);
271
g_task_run_in_thread (task, ide_unsaved_files_save_worker);
275
ide_unsaved_files_save_finish (IdeUnsavedFiles *files,
276
GAsyncResult *result,
279
g_return_val_if_fail (IDE_IS_UNSAVED_FILES (files), FALSE);
280
g_return_val_if_fail (G_IS_TASK (result), FALSE);
282
return g_task_propagate_boolean (G_TASK (result), error);
286
ide_unsaved_files_restore_worker (GTask *task,
287
gpointer source_object,
289
GCancellable *cancellable)
291
AsyncState *state = task_data;
292
g_autofree gchar *manifest_contents = NULL;
293
g_autofree gchar *manifest_path = NULL;
295
GError *error = NULL;
301
g_assert (G_IS_TASK (task));
302
g_assert (IDE_IS_UNSAVED_FILES (source_object));
305
manifest_path = g_build_filename (state->drafts_directory,
309
g_debug ("Loading drafts manifest %s", manifest_path);
311
if (!g_file_test (manifest_path, G_FILE_TEST_IS_REGULAR))
313
g_task_return_boolean (task, TRUE);
317
if (!g_file_get_contents (manifest_path, &manifest_contents, &len, &error))
319
g_task_return_error (task, error);
323
lines = g_strsplit (manifest_contents, "\n", 0);
325
for (i = 0; lines [i]; i++)
327
g_autoptr(GFile) file = NULL;
328
gchar *contents = NULL;
329
g_autofree gchar *hash = NULL;
330
g_autofree gchar *path = NULL;
331
UnsavedFile *unsaved;
337
file = g_file_new_for_uri (lines [i]);
341
hash = hash_uri (lines [i]);
342
path = g_build_filename (state->drafts_directory, hash, NULL);
344
g_debug ("Loading draft for \"%s\" from \"%s\"", lines [i], path);
346
if (!g_file_get_contents (path, &contents, &data_len, &error))
348
g_warning ("%s", error->message);
349
g_clear_error (&error);
353
unsaved = g_slice_new0 (UnsavedFile);
354
unsaved->file = g_object_ref (file);
355
unsaved->content = g_bytes_new_take (contents, data_len);
357
g_ptr_array_add (state->unsaved_files, unsaved);
362
g_task_return_boolean (task, TRUE);
366
ide_unsaved_files_restore_async (IdeUnsavedFiles *files,
367
GCancellable *cancellable,
368
GAsyncReadyCallback callback,
371
g_autoptr(GTask) task = NULL;
374
g_return_if_fail (IDE_IS_UNSAVED_FILES (files));
375
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
376
g_return_if_fail (callback);
378
state = async_state_new (files);
380
task = g_task_new (files, cancellable, callback, user_data);
381
g_task_set_task_data (task, state, async_state_free);
382
g_task_run_in_thread (task, ide_unsaved_files_restore_worker);
386
ide_unsaved_files_restore_finish (IdeUnsavedFiles *files,
387
GAsyncResult *result,
393
g_return_val_if_fail (IDE_IS_UNSAVED_FILES (files), FALSE);
394
g_return_val_if_fail (G_IS_TASK (result), FALSE);
396
state = g_task_get_task_data (G_TASK (result));
398
for (i = 0; i < state->unsaved_files->len; i++)
402
uf = g_ptr_array_index (state->unsaved_files, i);
403
ide_unsaved_files_update (files, uf->file, uf->content);
406
return g_task_propagate_boolean (G_TASK (result), error);
410
ide_unsaved_files_move_to_front (IdeUnsavedFiles *self,
413
IdeUnsavedFilesPrivate *priv = ide_unsaved_files_get_instance_private (self);
414
UnsavedFile *new_front;
415
UnsavedFile *old_front;
417
g_return_if_fail (IDE_IS_UNSAVED_FILES (self));
419
new_front = g_ptr_array_index (priv->unsaved_files, index);
420
old_front = g_ptr_array_index (priv->unsaved_files, 0);
423
* TODO: We could shift all these items down, but it probably isnt' worth
424
* the effort. We will just move-to-front after a miss and ping
425
* pong the old item back to the front.
427
priv->unsaved_files->pdata[0] = new_front;
428
priv->unsaved_files->pdata[index] = old_front;
432
ide_unsaved_files_remove_draft (IdeUnsavedFiles *self,
436
g_autofree gchar *drafts_directory = NULL;
437
g_autofree gchar *uri = NULL;
438
g_autofree gchar *hash = NULL;
439
g_autofree gchar *path = NULL;
443
g_assert (IDE_IS_UNSAVED_FILES (self));
444
g_assert (G_IS_FILE (file));
446
context = ide_object_get_context (IDE_OBJECT (self));
447
drafts_directory = get_drafts_directory (context);
448
uri = g_file_get_uri (file);
449
hash = hash_uri (uri);
450
path = g_build_filename (drafts_directory, hash, NULL);
452
g_debug ("Removing draft for \"%s\"", uri);
460
ide_unsaved_files_remove (IdeUnsavedFiles *self,
463
IdeUnsavedFilesPrivate *priv = ide_unsaved_files_get_instance_private (self);
466
g_return_if_fail (IDE_IS_UNSAVED_FILES (self));
467
g_return_if_fail (G_IS_FILE (file));
469
for (i = 0; i < priv->unsaved_files->len; i++)
471
UnsavedFile *unsaved;
473
unsaved = g_ptr_array_index (priv->unsaved_files, i);
475
if (g_file_equal (file, unsaved->file))
477
ide_unsaved_files_remove_draft (self, file);
478
g_ptr_array_remove_index_fast (priv->unsaved_files, i);
485
setup_tempfile (GFile *file,
489
g_autofree gchar *name = NULL;
493
g_assert (G_IS_FILE (file));
495
g_assert (temp_path);
500
name = g_file_get_basename (file);
501
suffix = strrchr (name, '.') ?: "";
502
template = g_strdup_printf ("builder_codeassistant_XXXXXX%s", suffix);
503
*temp_fd = g_file_open_tmp (template, temp_path, NULL);
507
ide_unsaved_files_update (IdeUnsavedFiles *self,
511
IdeUnsavedFilesPrivate *priv = ide_unsaved_files_get_instance_private (self);
512
UnsavedFile *unsaved;
515
g_return_if_fail (IDE_IS_UNSAVED_FILES (self));
516
g_return_if_fail (G_IS_FILE (file));
522
ide_unsaved_files_remove (self, file);
526
for (i = 0; i < priv->unsaved_files->len; i++)
528
unsaved = g_ptr_array_index (priv->unsaved_files, i);
530
if (g_file_equal (file, unsaved->file))
532
if (content != unsaved->content)
534
g_clear_pointer (&unsaved->content, g_bytes_unref);
535
unsaved->content = g_bytes_ref (content);
536
unsaved->sequence = priv->sequence;
540
* A file that get's updated is the most likely to get updated on
541
* the next attempt. Therefore, we will simply move this entry to
542
* the beginning of the array to increase it's chances of being the
543
* first entry we check.
546
ide_unsaved_files_move_to_front (self, i);
552
unsaved = g_slice_new0 (UnsavedFile);
553
unsaved->file = g_object_ref (file);
554
unsaved->content = g_bytes_ref (content);
555
unsaved->sequence = priv->sequence;
556
setup_tempfile (file, &unsaved->temp_fd, &unsaved->temp_path);
558
g_ptr_array_insert (priv->unsaved_files, 0, unsaved);
562
* ide_unsaved_files_to_array:
564
* This retrieves all of the unsaved file buffers known to the context.
565
* These are handy if you need to pass modified state to parsers such as
568
* Call g_ptr_array_unref() on the resulting #GPtrArray when no longer in use.
570
* If you would like to hold onto an unsaved file instance, call
571
* ide_unsaved_file_ref() to increment it's reference count.
573
* Returns: (transfer container) (element-type IdeUnsavedFile*): A #GPtrArray
574
* containing #IdeUnsavedFile elements.
577
ide_unsaved_files_to_array (IdeUnsavedFiles *self)
579
IdeUnsavedFilesPrivate *priv;
583
g_return_val_if_fail (IDE_IS_UNSAVED_FILES (self), NULL);
585
priv = ide_unsaved_files_get_instance_private (self);
587
ar = g_ptr_array_new ();
588
g_ptr_array_set_free_func (ar, (GDestroyNotify)ide_unsaved_file_unref);
590
for (i = 0; i < priv->unsaved_files->len; i++)
592
IdeUnsavedFile *item;
595
uf = g_ptr_array_index (priv->unsaved_files, i);
596
item = _ide_unsaved_file_new (uf->file, uf->content, uf->temp_path, uf->sequence);
598
g_ptr_array_add (ar, item);
605
* ide_unsaved_files_get_unsaved_file:
607
* Retrieves the unsaved file content for a particular file. If no unsaved
608
* file content is registered, %NULL is returned.
610
* Returns: (nullable) (transfer full): An #IdeUnsavedFile or %NULL.
613
ide_unsaved_files_get_unsaved_file (IdeUnsavedFiles *self,
616
IdeUnsavedFilesPrivate *priv = ide_unsaved_files_get_instance_private (self);
617
IdeUnsavedFile *ret = NULL;
622
g_return_val_if_fail (IDE_IS_UNSAVED_FILES (self), NULL);
624
#ifdef IDE_ENABLE_TRACE
628
path = g_file_get_path (file);
629
IDE_TRACE_MSG ("%s", path);
634
for (i = 0; i < priv->unsaved_files->len; i++)
638
uf = g_ptr_array_index (priv->unsaved_files, i);
640
if (g_file_equal (uf->file, file))
642
IDE_TRACE_MSG ("Hit");
643
ret = _ide_unsaved_file_new (uf->file, uf->content, uf->temp_path, uf->sequence);
648
IDE_TRACE_MSG ("Miss");
655
ide_unsaved_files_get_sequence (IdeUnsavedFiles *self)
657
IdeUnsavedFilesPrivate *priv = ide_unsaved_files_get_instance_private (self);
659
g_return_val_if_fail (IDE_IS_UNSAVED_FILES (self), -1);
661
return priv->sequence;
665
ide_unsaved_files_finalize (GObject *object)
667
IdeUnsavedFiles *self = (IdeUnsavedFiles *)object;
668
IdeUnsavedFilesPrivate *priv = ide_unsaved_files_get_instance_private (self);
670
g_clear_pointer (&priv->unsaved_files, g_ptr_array_unref);
672
G_OBJECT_CLASS (ide_unsaved_files_parent_class)->finalize (object);
676
ide_unsaved_files_class_init (IdeUnsavedFilesClass *klass)
678
GObjectClass *object_class = G_OBJECT_CLASS (klass);
680
object_class->finalize = ide_unsaved_files_finalize;
684
ide_unsaved_files_init (IdeUnsavedFiles *self)
686
IdeUnsavedFilesPrivate *priv = ide_unsaved_files_get_instance_private (self);
688
priv->unsaved_files = g_ptr_array_new_with_free_func (unsaved_file_free);
692
ide_unsaved_files_clear (IdeUnsavedFiles *self)
694
g_autoptr(GPtrArray) ar = NULL;
697
g_return_if_fail (IDE_IS_UNSAVED_FILES (self));
699
ar = ide_unsaved_files_to_array (self);
701
for (i = 0; i < ar->len; i++)
706
uf = g_ptr_array_index (ar, i);
707
file = ide_unsaved_file_get_file (uf);
708
ide_unsaved_files_remove (self, file);