/* * fm-file-ops-job-delete.c * * Copyright 2009 Hong Jen Yee (PCMan) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include "fm-file-ops-job-delete.h" #include "fm-file-ops-job-xfer.h" #include "fm-monitor.h" static const char query[] = G_FILE_ATTRIBUTE_STANDARD_TYPE"," G_FILE_ATTRIBUTE_STANDARD_NAME"," G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME; /* FIXME: cancel the job on errors */ gboolean _fm_file_ops_job_delete_file(FmJob* job, GFile* gf, GFileInfo* inf) { GError* err = NULL; FmFileOpsJob* fjob = (FmFileOpsJob*)job; gboolean is_dir, descend; GFileInfo* _inf = NULL; gboolean ret = FALSE; if( !inf) { _retry_query_info: _inf = inf = g_file_query_info(gf, query, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, fm_job_get_cancellable(job), &err); if(!_inf) { FmJobErrorAction act = fm_job_emit_error(job, err, FM_JOB_ERROR_MODERATE); g_error_free(err); err = NULL; if(act == FM_JOB_RETRY) goto _retry_query_info; else if(act == FM_JOB_ABORT) return FALSE; } } if(!inf) { /* use basename of GFile as display name. */ char* basename = g_file_get_basename(gf); char* disp = g_filename_display_name(basename); g_free(basename); fm_file_ops_job_emit_cur_file(fjob, disp); g_free(disp); ++fjob->finished; return FALSE; } /* currently processed file. */ fm_file_ops_job_emit_cur_file(fjob, g_file_info_get_display_name(inf)); /* show progress */ ++fjob->finished; fm_file_ops_job_emit_percent(FM_FILE_OPS_JOB(job)); is_dir = (g_file_info_get_file_type(inf)==G_FILE_TYPE_DIRECTORY); if(_inf) g_object_unref(_inf); if( fm_job_is_cancelled(job) ) return FALSE; if(is_dir) { descend = TRUE; /* special handling for trash:/// */ if(!g_file_is_native(gf)) { char* scheme = g_file_get_uri_scheme(gf); if(g_strcmp0(scheme, "trash") == 0) { /* little trick: basename of trash root is /. */ char* basename = g_file_get_basename(gf); if(basename[0] != G_DIR_SEPARATOR) descend = FALSE; g_free(basename); } g_free(scheme); } } else descend = FALSE; if( descend ) { GFileMonitor* old_mon = fjob->src_folder_mon; GFileEnumerator* enu = g_file_enumerate_children(gf, query, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, fm_job_get_cancellable(job), &err); if(!enu) { FmJobErrorAction act = fm_job_emit_error(job, err, FM_JOB_ERROR_MODERATE); g_error_free(err); err = NULL; return FALSE; } if(! g_file_is_native(gf)) fjob->src_folder_mon = fm_monitor_lookup_dummy_monitor(gf); while( ! fm_job_is_cancelled(job) ) { inf = g_file_enumerator_next_file(enu, fm_job_get_cancellable(job), &err); if(inf) { GFile* sub = g_file_get_child(gf, g_file_info_get_name(inf)); _fm_file_ops_job_delete_file(job, sub, inf); /* FIXME: error handling? */ g_object_unref(sub); g_object_unref(inf); } else { if(err) { FmJobErrorAction act = fm_job_emit_error(job, err, FM_JOB_ERROR_MODERATE); /* FM_JOB_RETRY is not supported here */ g_error_free(err); err = NULL; g_object_unref(enu); if(fjob->src_folder_mon) g_object_unref(fjob->src_folder_mon); fjob->src_folder_mon = old_mon; return FALSE; } else /* EOF */ break; } } g_object_unref(enu); if(fjob->src_folder_mon) { /* FIXME: this is a little bit incorrect since we emit deleted signal before the * dir is really deleted. */ g_file_monitor_emit_event(fjob->src_folder_mon, gf, NULL, G_FILE_MONITOR_EVENT_DELETED); g_object_unref(fjob->src_folder_mon); } fjob->src_folder_mon = old_mon; } if(!fm_job_is_cancelled(job)) { _retry_delete: ret = g_file_delete(gf, fm_job_get_cancellable(job), &err); if(ret) { if(fjob->src_folder_mon) g_file_monitor_emit_event(fjob->src_folder_mon, gf, NULL, G_FILE_MONITOR_EVENT_DELETED); } else { if(err) { FmJobErrorAction act; if(err->domain == G_IO_ERROR && err->code == G_IO_ERROR_PERMISSION_DENIED) { /* special case for trash:/// */ /* FIXME: is there any better way to handle this? */ char* scheme = g_file_get_uri_scheme(gf); if(g_strcmp0(scheme, "trash") == 0) { g_free(scheme); g_error_free(err); err = NULL; return TRUE; } g_free(scheme); } act = fm_job_emit_error(job, err, FM_JOB_ERROR_MODERATE); g_error_free(err); err = NULL; if(act == FM_JOB_RETRY) goto _retry_delete; return FALSE; } } } else ret = FALSE; return ret; } gboolean _fm_file_ops_job_delete_run(FmFileOpsJob* job) { GList* l; gboolean ret = TRUE; /* prepare the job, count total work needed with FmDeepCountJob */ FmDeepCountJob* dc = fm_deep_count_job_new(job->srcs, FM_DC_JOB_PREPARE_DELETE); /* let the deep count job share the same cancellable */ fm_job_set_cancellable(dc, fm_job_get_cancellable(FM_JOB(job))); fm_job_run_sync(FM_JOB(dc)); job->total = dc->count; g_object_unref(dc); if(fm_job_is_cancelled(FM_JOB(job))) { g_debug("delete job is cancelled"); return FALSE; } g_debug("total number of files to delete: %llu", job->total); fm_file_ops_job_emit_prepared(job); l = fm_list_peek_head_link(job->srcs); for(; ! fm_job_is_cancelled(FM_JOB(job)) && l;l=l->next) { GFileMonitor* mon; GFile* src = fm_path_to_gfile((FmPath*)l->data); if( g_file_is_native(src) ) mon = NULL; else { GFile* src_dir = g_file_get_parent(src); if(src_dir) { mon = fm_monitor_lookup_dummy_monitor(src_dir); job->src_folder_mon = mon; g_object_unref(src_dir); } else job->src_folder_mon = mon = NULL; } ret = _fm_file_ops_job_delete_file(FM_JOB(job), src, NULL); g_object_unref(src); if(mon) { g_object_unref(mon); job->src_folder_mon = NULL; } } return ret; } gboolean _fm_file_ops_job_trash_run(FmFileOpsJob* job) { gboolean ret = TRUE; GList* l; FmPathList* unsupported = fm_path_list_new(); GError* err = NULL; FmJob* fmjob = FM_JOB(job); g_debug("total number of files to delete: %u", fm_list_get_length(job->srcs)); job->total = fm_list_get_length(job->srcs); fm_file_ops_job_emit_prepared(job); /* FIXME: we shouldn't trash a file already in trash:/// */ l = fm_list_peek_head_link(job->srcs); for(; !fm_job_is_cancelled(fmjob) && l;l=l->next) { GFile* gf = fm_path_to_gfile((FmPath*)l->data); _retry_trash: ret = g_file_trash(gf, fm_job_get_cancellable(fmjob), &err); g_object_unref(gf); if(!ret) { /* if trashing is not supported by the file system */ if( err->domain == G_IO_ERROR && err->code == G_IO_ERROR_NOT_SUPPORTED) fm_list_push_tail(unsupported, FM_PATH(l->data)); else { FmJobErrorAction act = fm_job_emit_error(job, err, FM_JOB_ERROR_MODERATE); g_error_free(err); err = NULL; if(act == FM_JOB_RETRY) goto _retry_trash; else if(act == FM_JOB_ABORT) return FALSE; } g_error_free(err); err = NULL; } ++job->finished; fm_file_ops_job_emit_percent(job); } /* these files cannot be trashed due to lack of support from * underlying file systems. */ if(fm_list_is_empty(unsupported)) fm_list_unref(unsupported); else { /* FIXME: this is a dirty hack to fallback to delete if trash is not available. * The API must be re-designed later. */ g_object_set_data_full(job, "trash-unsupported", unsupported, fm_list_unref); } return TRUE; } static const char trash_query[]= G_FILE_ATTRIBUTE_STANDARD_TYPE"," G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME"," G_FILE_ATTRIBUTE_STANDARD_NAME"," G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL"," G_FILE_ATTRIBUTE_STANDARD_SIZE"," G_FILE_ATTRIBUTE_UNIX_BLOCKS"," G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE"," G_FILE_ATTRIBUTE_ID_FILESYSTEM"," "trash::orig-path"; static gboolean ensure_parent_dir(FmJob* job, GFile* orig_path) { GFile* parent = g_file_get_parent(orig_path); gboolean ret = g_file_query_exists(parent, fm_job_get_cancellable(job)); if(!ret) { GError* err = NULL; _retry_mkdir: if(!g_file_make_directory_with_parents(parent, fm_job_get_cancellable(job), &err)) { if(!fm_job_is_cancelled(job)) { FmJobErrorAction act = fm_job_emit_error(job, err, FM_JOB_ERROR_MODERATE); g_error_free(err); err = NULL; if(act == FM_JOB_RETRY) goto _retry_mkdir; } } else ret = TRUE; } g_object_unref(parent); return ret; } gboolean _fm_file_ops_job_untrash_run(FmFileOpsJob* job) { gboolean ret = TRUE; GList* l; GError* err = NULL; FmJob* fmjob = FM_JOB(job); job->total = fm_list_get_length(job->srcs); fm_file_ops_job_emit_prepared(job); l = fm_list_peek_head_link(job->srcs); for(; !fm_job_is_cancelled(fmjob) && l;l=l->next) { GFile* gf; GFileInfo* inf; FmPath* path = FM_PATH(l->data); if(!fm_path_is_trash(path)) continue; gf = fm_path_to_gfile(path); _retry_get_orig_path: inf = g_file_query_info(gf, trash_query, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, fm_job_get_cancellable(fmjob), &err); if(inf) { char* orig_path_str = g_file_info_get_attribute_as_string(inf, "trash::orig-path"); if(orig_path_str) { GFile* orig_path = g_file_new_for_commandline_arg(orig_path_str); g_free(orig_path_str); /* ensure the existence of parent folder. */ if(ensure_parent_dir(job, orig_path)) ret = _fm_file_ops_job_move_file(job, gf, inf, orig_path); } g_object_unref(inf); } else { if(err) { FmJobErrorAction act = fm_job_emit_error(job, err, FM_JOB_ERROR_MODERATE); g_error_free(err); err = NULL; if(act == FM_JOB_RETRY) goto _retry_get_orig_path; else if(act == FM_JOB_ABORT) { g_object_unref(gf); return FALSE; } } } ++job->finished; fm_file_ops_job_emit_percent(job); } return TRUE; }