4
* Copyright 2009 PCMan <pcman.tw@gmail.com>
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22
#include "fm-folder.h"
23
#include "fm-monitor.h"
24
#include "fm-marshal.h"
37
static FmFolder* fm_folder_new_internal(FmPath* path, GFile* gf);
38
static FmFolder* fm_folder_get_internal(FmPath* path, GFile* gf);
39
static void fm_folder_finalize (GObject *object);
41
static void on_file_info_finished(FmFileInfoJob* job, FmFolder* folder);
42
static gboolean on_idle(FmFolder* folder);
44
G_DEFINE_TYPE(FmFolder, fm_folder, G_TYPE_OBJECT);
46
static GList* _fm_folder_get_file_by_name(FmFolder* folder, const char* name);
48
static guint signals[N_SIGNALS];
49
static GHashTable* hash = NULL; /* FIXME: should this be guarded with a mutex? */
51
static void fm_folder_class_init(FmFolderClass *klass)
53
GObjectClass *g_object_class;
54
g_object_class = G_OBJECT_CLASS(klass);
55
g_object_class->finalize = fm_folder_finalize;
56
fm_folder_parent_class = (GObjectClass*)g_type_class_peek(G_TYPE_OBJECT);
59
* files-added is emitted when there is a new file created in the dir.
60
* The param is GList* of the newly added file.
62
signals[ FILES_ADDED ] =
63
g_signal_new ( "files-added",
64
G_TYPE_FROM_CLASS ( klass ),
66
G_STRUCT_OFFSET ( FmFolderClass, files_added ),
68
g_cclosure_marshal_VOID__POINTER,
69
G_TYPE_NONE, 1, G_TYPE_POINTER );
72
* files-removed is emitted when there is a file deleted in the dir.
73
* The param is GList* of the removed files.
75
signals[ FILES_REMOVED ] =
76
g_signal_new ( "files-removed",
77
G_TYPE_FROM_CLASS ( klass ),
79
G_STRUCT_OFFSET ( FmFolderClass, files_removed ),
81
g_cclosure_marshal_VOID__POINTER,
82
G_TYPE_NONE, 1, G_TYPE_POINTER );
85
* file-changed is emitted when there is a file changed in the dir.
86
* The param is VFSFileInfo of the newly created file.
88
signals[ FILES_CHANGED ] =
89
g_signal_new ( "files-changed",
90
G_TYPE_FROM_CLASS ( klass ),
92
G_STRUCT_OFFSET ( FmFolderClass, files_changed ),
94
g_cclosure_marshal_VOID__POINTER,
95
G_TYPE_NONE, 1, G_TYPE_POINTER );
98
g_signal_new ( "loaded",
99
G_TYPE_FROM_CLASS ( klass ),
101
G_STRUCT_OFFSET ( FmFolderClass, loaded ),
103
g_cclosure_marshal_VOID__VOID,
107
g_signal_new ( "unmount",
108
G_TYPE_FROM_CLASS ( klass ),
110
G_STRUCT_OFFSET ( FmFolderClass, unmount ),
112
g_cclosure_marshal_VOID__VOID,
116
g_signal_new( "error",
117
G_TYPE_FROM_CLASS ( klass ),
119
G_STRUCT_OFFSET ( FmFolderClass, error ),
121
fm_marshal_INT__POINTER_INT,
122
G_TYPE_INT, 2, G_TYPE_POINTER, G_TYPE_INT );
126
static void fm_folder_init(FmFolder *self)
128
self->files = fm_file_info_list_new();
131
void on_file_info_finished(FmFileInfoJob* job, FmFolder* folder)
134
GSList* files_to_add = NULL;
135
GSList* files_to_update = NULL;
137
for(l=fm_list_peek_head_link(job->file_infos);l;l=l->next)
139
FmFileInfo* fi = (FmFileInfo*)l->data;
140
GList* l2 = _fm_folder_get_file_by_name(folder, fi->path->name);
141
if(l2) /* the file is already in the folder, update */
143
FmFileInfo* fi2 = (FmFileInfo*)l2->data;
144
/* FIXME: will fm_file_info_copy here cause problems?
145
* the file info might be referenced by others, too.
146
* we're mofifying an object referenced by others.
147
* we should redesign the API, or document this clearly
150
fm_file_info_copy(fi2, fi);
151
files_to_update = g_slist_prepend(files_to_update, fi2);
155
files_to_add = g_slist_prepend(files_to_add, fi);
156
fm_file_info_ref(fi);
157
fm_list_push_tail(folder->files, fi);
162
g_signal_emit(folder, signals[FILES_ADDED], 0, files_to_add);
163
g_slist_free(files_to_add);
167
g_signal_emit(folder, signals[FILES_CHANGED], 0, files_to_update);
168
g_slist_free(files_to_update);
171
folder->pending_jobs = g_slist_remove(folder->pending_jobs, job);
174
gboolean on_idle(FmFolder* folder)
177
FmFileInfoJob* job = NULL;
179
folder->idle_handler = 0;
180
if(folder->files_to_update || folder->files_to_add)
181
job = (FmFileInfoJob*)fm_file_info_job_new(NULL, 0);
183
if(folder->files_to_update)
186
for(l=folder->files_to_update;l;)
188
/* if a file is already in files_to_add, remove it. */
189
if(g_slist_find_custom(folder->files_to_add, l->data, (GCompareFunc)strcmp))
197
if(G_UNLIKELY(tmp == folder->files_to_update))
198
folder->files_to_update = l;
201
path = fm_path_new_child(folder->dir_path, (char*)l->data);
202
fm_file_info_job_add(job, path);
209
g_slist_free(folder->files_to_update);
210
folder->files_to_update = NULL;
213
if(folder->files_to_add)
215
for(l=folder->files_to_add;l;l=l->next)
217
path = fm_path_new_child(folder->dir_path, (char*)l->data);
218
fm_file_info_job_add(job, path);
222
g_slist_free(folder->files_to_add);
223
folder->files_to_add = NULL;
228
g_signal_connect(job, "finished", on_file_info_finished, folder);
229
folder->pending_jobs = g_slist_prepend(folder->pending_jobs, job);
230
fm_job_run_async(FM_JOB(job));
233
if(folder->files_to_del)
236
for(ll=folder->files_to_del;ll;ll=ll->next)
238
GList* l= (GList*)ll->data;
240
fm_list_delete_link_nounref(folder->files , l);
242
g_signal_emit(folder, signals[FILES_REMOVED], 0, folder->files_to_del);
243
g_slist_foreach(folder->files_to_del, (GFunc)fm_file_info_unref, NULL);
244
g_slist_free(folder->files_to_del);
245
folder->files_to_del = NULL;
251
static void on_folder_changed(GFileMonitor* mon, GFile* gf, GFile* other, GFileMonitorEvent evt, FmFolder* folder)
256
const char* names[]={
257
"G_FILE_MONITOR_EVENT_CHANGED",
258
"G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT",
259
"G_FILE_MONITOR_EVENT_DELETED",
260
"G_FILE_MONITOR_EVENT_CREATED",
261
"G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED",
262
"G_FILE_MONITOR_EVENT_PRE_UNMOUNT",
263
"G_FILE_MONITOR_EVENT_UNMOUNTED"
265
name = g_file_get_basename(gf);
266
g_debug("folder: %p, file %s event: %s", folder, name, names[evt]);
270
if(g_file_equal(gf, folder->gf))
272
g_debug("event of the folder itself: %d", evt);
273
/* FIXME: handle unmount events */
274
if(evt == G_FILE_MONITOR_EVENT_PRE_UNMOUNT)
276
g_debug("folder is going to be unmounted");
278
else if(evt == G_FILE_MONITOR_EVENT_UNMOUNTED)
280
g_signal_emit(folder, signals[UNMOUNT], 0);
281
g_debug("folder is unmounted");
286
name = g_file_get_basename(gf);
289
case G_FILE_MONITOR_EVENT_CREATED:
290
folder->files_to_add = g_slist_append(folder->files_to_add, name);
292
case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
293
case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
294
folder->files_to_update = g_slist_append(folder->files_to_update, name);
296
case G_FILE_MONITOR_EVENT_DELETED:
297
l = _fm_folder_get_file_by_name(folder, name);
298
if(l && !g_slist_find(folder->files_to_del, l) )
299
folder->files_to_del = g_slist_prepend(folder->files_to_del, l);
303
/* g_debug("folder %p %s event: %s", folder, name, names[evt]); */
307
if(!folder->idle_handler)
308
folder->idle_handler = g_idle_add_full(G_PRIORITY_LOW, on_idle, folder, NULL);
311
static void on_job_finished(FmDirListJob* job, FmFolder* folder)
314
GSList* files = NULL;
315
/* actually manually disconnecting from 'finished' signal is not
316
* needed since the signal is only emit once, and later the job
317
* object will be distroyed very soon. */
318
/* g_signal_handlers_disconnect_by_func(job, on_job_finished, folder); */
319
for(l = fm_list_peek_head_link(job->files); l; l=l->next)
321
FmFileInfo* inf = (FmFileInfo*)l->data;
322
files = g_slist_prepend(files, inf);
323
fm_list_push_tail(folder->files, inf);
326
g_signal_emit(folder, signals[FILES_ADDED], 0, files);
329
folder->dir_fi = fm_file_info_ref(job->dir_fi);
331
folder->job = NULL; /* the job object will be freed in idle handler. */
332
g_signal_emit(folder, signals[LOADED], 0);
335
static FmJobErrorAction on_job_err(FmDirListJob* job, GError* err, FmJobErrorSeverity severity, FmFolder* folder)
337
FmJobErrorAction ret;
338
g_signal_emit(folder, signals[ERROR], 0, err, severity, &ret);
342
FmFolder* fm_folder_new_internal(FmPath* path, GFile* gf)
345
FmFolder* folder = (FmFolder*)g_object_new(FM_TYPE_FOLDER, NULL);
346
folder->dir_path = fm_path_ref(path);
348
folder->gf = (GFile*)g_object_ref(gf);
350
folder->mon = fm_monitor_directory(gf, &err);
352
g_signal_connect(folder->mon, "changed", G_CALLBACK(on_folder_changed), folder );
356
fm_folder_reload(folder);
360
FmFolder* fm_folder_get_internal(FmPath* path, GFile* gf)
363
/* FIXME: should we provide a generic FmPath cache in fm-path.c
364
* to associate all kinds of data structures with FmPaths? */
366
/* FIXME: should creation of the hash table be moved to fm_init()? */
368
folder = (FmFolder*)g_hash_table_lookup(hash, path);
371
hash = g_hash_table_new((GHashFunc)fm_path_hash, (GEqualFunc)fm_path_equal);
375
if( G_UNLIKELY(!folder) )
379
_gf = gf = fm_path_to_gfile(path);
380
folder = fm_folder_new_internal(path, gf);
383
g_hash_table_insert(hash, folder->dir_path, folder);
386
return (FmFolder*)g_object_ref(folder);
390
static void fm_folder_finalize(GObject *object)
393
g_return_if_fail(object != NULL);
394
g_return_if_fail(FM_IS_FOLDER(object));
396
self = FM_FOLDER(object);
400
g_signal_handlers_disconnect_by_func(self->job, on_job_finished, self);
401
g_signal_handlers_disconnect_by_func(self->job, on_job_err, self);
402
fm_job_cancel(FM_JOB(self->job)); /* FIXME: is this ok? */
403
/* the job will be freed automatically in idle handler. */
406
if(self->pending_jobs)
409
for(l = self->pending_jobs;l;l=l->next)
411
FmJob* job = FM_JOB(l->data);
412
g_signal_handlers_disconnect_by_func(job, on_job_finished, self);
414
/* the job will be freed automatically in idle handler. */
418
/* remove from hash table */
419
g_hash_table_remove(hash, self->dir_path);
421
fm_path_unref(self->dir_path);
424
fm_file_info_unref(self->dir_fi);
427
g_object_unref(self->gf);
431
g_signal_handlers_disconnect_by_func(self->mon, on_folder_changed, self);
432
g_object_unref(self->mon);
435
if(self->idle_handler)
437
g_source_remove(self->idle_handler);
438
if(self->files_to_add)
440
g_slist_foreach(self->files_to_add, (GFunc)g_free, NULL);
441
g_slist_free(self->files_to_add);
443
if(self->files_to_update)
445
g_slist_foreach(self->files_to_update, (GFunc)g_free, NULL);
446
g_slist_free(self->files_to_update);
448
if(self->files_to_del)
449
g_slist_free(self->files_to_del);
452
fm_list_unref(self->files);
454
if (G_OBJECT_CLASS(fm_folder_parent_class)->finalize)
455
(* G_OBJECT_CLASS(fm_folder_parent_class)->finalize)(object);
458
FmFolder* fm_folder_get_for_gfile (GFile* gf)
460
FmPath* path = fm_path_new_for_gfile(gf);
461
FmFolder* folder = fm_folder_new_internal(path, gf);
466
FmFolder* fm_folder_get_for_path(FmPath* path)
468
return fm_folder_get_internal(path, NULL);
471
FmFolder* fm_folder_get_for_path_name(const char* path)
473
FmPath* fm_path = fm_path_new(path);
474
FmFolder* folder = fm_folder_get_internal(fm_path, NULL);
475
fm_path_unref(fm_path);
479
/* FIXME: should we use GFile here? */
480
FmFolder* fm_folder_get_for_uri (const char* uri)
482
GFile* gf = g_file_new_for_uri(uri);
483
FmFolder* folder = fm_folder_get_for_gfile(gf);
488
void fm_folder_reload(FmFolder* folder)
490
/* FIXME: remove all items and re-run a dir list job. */
491
GSList* files_to_del = NULL;
492
GList* l = fm_list_peek_head_link(folder->files);
497
FmFileInfo* fi = (FmFileInfo*)l->data;
498
files_to_del = g_slist_prepend(files_to_del, fi);
500
g_signal_emit(folder, signals[FILES_REMOVED], 0, files_to_del);
501
fm_list_clear(folder->files); /* fm_file_info_unref will be invoked. */
502
g_slist_free(files_to_del);
505
folder->job = fm_dir_list_job_new(folder->dir_path, FALSE);
506
g_signal_connect(folder->job, "finished", G_CALLBACK(on_job_finished), folder);
507
g_signal_connect(folder->job, "error", G_CALLBACK(on_job_err), folder);
508
fm_job_run_async(FM_JOB(folder->job));
511
FmFileInfoList* fm_folder_get_files (FmFolder* folder)
513
return folder->files;
516
GList* _fm_folder_get_file_by_name(FmFolder* folder, const char* name)
518
GList* l = fm_list_peek_head_link(folder->files);
521
FmFileInfo* fi = (FmFileInfo*)l->data;
522
if(strcmp(fi->path->name, name) == 0)
528
FmFileInfo* fm_folder_get_file_by_name(FmFolder* folder, const char* name)
530
GList* l = _fm_folder_get_file_by_name(folder, name);
531
return l ? (FmFileInfo*)l->data : NULL;
534
FmFolder* fm_folder_get(FmPath* path)
536
return fm_folder_get_internal(path, NULL);
539
gboolean fm_folder_get_is_loading(FmFolder* folder)
541
return (folder->job != NULL);