From 634f26b04f35af1ac41a545fc012936ad0f6ce67 Mon Sep 17 00:00:00 2001 From: Nicholas Hughart Date: Mon, 21 Jul 2008 04:12:39 +0000 Subject: [PATCH] New efm_op stuff from ptomaine. Test it out, try many different cases of copying/moving/removing etc. Right now progress for operations is just printed on STDOUT, but this will hopefully change soon (Something I've started already). If you find any bugs, send them to the list and/or notify ptomaine and/or myself. If someone wants to be a formatting nazi go ahead and check the formatting, I gave it a quick scan and it seemed close. More to come :) SVN revision: 35178 --- src/bin/Makefile.am | 13 +- src/bin/e_fm.c | 210 ++++++- src/bin/e_fm_main.c | 381 ++++++++++-- src/bin/e_fm_op.c | 1384 +++++++++++++++++++++++++++++++++++++++++++ src/bin/e_fm_op.h | 32 + 5 files changed, 1956 insertions(+), 64 deletions(-) create mode 100644 src/bin/e_fm_op.c create mode 100644 src/bin/e_fm_op.h diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am index 9601fe500..cdbe5fb86 100644 --- a/src/bin/Makefile.am +++ b/src/bin/Makefile.am @@ -21,7 +21,8 @@ enlightenment_start \ enlightenment_thumb \ enlightenment_sys \ enlightenment_fm \ -enlightenment_init +enlightenment_init \ +enlightenment_fm_op ENLIGHTENMENTHEADERS = \ e.h \ @@ -131,6 +132,7 @@ e_confirm_dialog.h \ e_int_border_prop.h \ e_entry_dialog.h \ e_fm.h \ +e_fm_op.h \ e_fm_hal.h \ e_widget_scrollframe.h \ e_sha1.h \ @@ -337,11 +339,18 @@ enlightenment_thumb_LDFLAGS = @e_libs@ enlightenment_fm_SOURCES = \ e_fm_main.c \ e_user.c \ -e_sha1.c +e_sha1.c \ +e_prefix.c enlightenment_fm_LDFLAGS = @e_libs@ @dlopen_libs@ @E_DBUS_LIBS@ @E_HAL_LIBS@ enlightenment_fm_CFLAGS = $(INCLUDES) @E_HAL_CFLAGS@ +enlightenment_fm_op_SOURCES = \ +e_fm_op.c + +enlightenment_fm_op_LDFLAGS = @e_libs@ +enlightenment_fm_op_CFLAGS = $(INCLUDES) + enlightenment_sys_SOURCES = \ e_sys_main.c diff --git a/src/bin/e_fm.c b/src/bin/e_fm.c index b9430c71f..4ede60e9b 100644 --- a/src/bin/e_fm.c +++ b/src/bin/e_fm.c @@ -3,6 +3,7 @@ */ #include "e.h" #include "e_fm_hal.h" +#include "e_fm_op.h" #define OVERCLIP 128 @@ -300,6 +301,23 @@ static void _e_fm2_file_rename_yes_cb(char *text, void *data); static void _e_fm2_file_rename_no_cb(void *data); static void _e_fm2_file_properties(void *data, E_Menu *m, E_Menu_Item *mi); static void _e_fm2_file_properties_delete_cb(void *obj); + +static void _e_fm_overwrite_dialog(int pid, const char *str); +static void _e_fm_overwrite_delete_cb(void *obj); +static void _e_fm_send_overwrite_response(int id, E_Fm_Op_Type type); +static void _e_fm_overwrite_no_cb(void *data, E_Dialog *dialog); +static void _e_fm_overwrite_no_all_cb(void *data, E_Dialog *dialog); +static void _e_fm_overwrite_yes_cb(void *data, E_Dialog *dialog); +static void _e_fm_overwrite_yes_all_cb(void *data, E_Dialog *dialog); + +static void _e_fm_error_dialog(int pid, const char *str); +static void _e_fm_error_delete_cb(void *obj); +static void _e_fm_send_error_response(int id, E_Fm_Op_Type type); +static void _e_fm_error_retry_cb(void *data, E_Dialog *dialog); +static void _e_fm_error_abort_cb(void *data, E_Dialog *dialog); +static void _e_fm_error_ignore_this_cb(void *data, E_Dialog *dialog); +static void _e_fm_error_ignore_all_cb(void *data, E_Dialog *dialog); + static void _e_fm2_file_delete(void *data, E_Menu *m, E_Menu_Item *mi); static void _e_fm2_file_delete_delete_cb(void *obj); static void _e_fm2_file_delete_yes_cb(void *data, E_Dialog *dialog); @@ -2233,8 +2251,29 @@ e_fm2_client_data(Ecore_Ipc_Event_Client_Data *e) v->mount_point = NULL; } } + case 14:/*error*/ + printf("%s:%s(%d) Error from slave #%d: %s\n", __FILE__, __FUNCTION__, __LINE__, e->ref, e->data); + _e_fm_error_dialog(e->ref, e->data); break; - default: + + case 15:/*overwrite*/ + printf("%s:%s(%d) Overwrite from slave #%d: %s\n", __FILE__, __FUNCTION__, __LINE__, e->ref, e->data); + _e_fm_overwrite_dialog(e->ref, e->data); + break; + + case 16:/*progress*/ + { + int percent, seconds; + + if(!e->data || e->size != 2 * sizeof(int)) return; + + percent = *(int *)e->data; + seconds = *(int *)(e->data + sizeof(int)); + printf("%s:%s(%d) Progress from slave #%d: %d%% done, %d seconds left.\n", __FILE__, __FUNCTION__, __LINE__, e->ref, percent, seconds); + break; + } + + default: break; } } @@ -6974,6 +7013,175 @@ _e_fm2_file_rename_no_cb(void *data) ic->entry_dialog = NULL; } +static void +_e_fm_overwrite_dialog(int pid, const char *str) +{ + E_Manager *man; + E_Container *con; + E_Dialog *dialog; + int *id; + char text[4096 + PATH_MAX]; + + man = e_manager_current_get(); + if (!man) return; + con = e_container_current_get(man); + if (!con) return; + + id = malloc(sizeof(int)); + *id = pid; + + dialog = e_dialog_new(con, "E", "_fm_overwrite_dialog"); + E_OBJECT(dialog)->data = id; + e_object_del_attach_func_set(E_OBJECT(dialog), _e_fm_overwrite_delete_cb); + e_dialog_button_add(dialog, _("No"), NULL, _e_fm_overwrite_no_cb, NULL); + e_dialog_button_add(dialog, _("No to all"), NULL, _e_fm_overwrite_no_all_cb, NULL); + e_dialog_button_add(dialog, _("Yes"), NULL, _e_fm_overwrite_yes_cb, NULL); + e_dialog_button_add(dialog, _("Yes to all"), NULL, _e_fm_overwrite_yes_all_cb, NULL); + + e_dialog_button_focus_num(dialog, 0); + e_dialog_title_set(dialog, _("Error")); + snprintf(text, sizeof(text), + _("%s"), + str); + + e_dialog_text_set(dialog, text); + e_win_centered_set(dialog->win, 1); + e_dialog_show(dialog); +} + +static void +_e_fm_overwrite_delete_cb(void *obj) +{ + int *id = E_OBJECT(obj)->data; + free(id); +} + +/* TODO: merge _e_fm_send_overwrite_response() and _e_fm_send_error_response() (???) */ + +static void +_e_fm_send_overwrite_response(int id, E_Fm_Op_Type type) +{ + ecore_ipc_client_send(_e_fm2_client_get()->cl, E_IPC_DOMAIN_FM, 15, + id, 0, 0, + &type, sizeof(E_Fm_Op_Type)); +} + +static void +_e_fm_overwrite_no_cb(void *data, E_Dialog *dialog) +{ + int *id = E_OBJECT(dialog)->data; + _e_fm_send_overwrite_response(*id, E_FM_OP_OVERWRITE_RESPONSE_NO); + e_object_del(E_OBJECT(dialog)); +} + +static void +_e_fm_overwrite_no_all_cb(void *data, E_Dialog *dialog) +{ + int *id = E_OBJECT(dialog)->data; + _e_fm_send_overwrite_response(*id, E_FM_OP_OVERWRITE_RESPONSE_NO_ALL); + e_object_del(E_OBJECT(dialog)); +} + +static void +_e_fm_overwrite_yes_cb(void *data, E_Dialog *dialog) +{ + int *id = E_OBJECT(dialog)->data; + _e_fm_send_overwrite_response(*id, E_FM_OP_OVERWRITE_RESPONSE_YES); + e_object_del(E_OBJECT(dialog)); +} + +static void +_e_fm_overwrite_yes_all_cb(void *data, E_Dialog *dialog) +{ + int *id = E_OBJECT(dialog)->data; + _e_fm_send_overwrite_response(*id, E_FM_OP_OVERWRITE_RESPONSE_YES_ALL); + e_object_del(E_OBJECT(dialog)); +} + +static void +_e_fm_error_dialog(int pid, const char *str) +{ + E_Manager *man; + E_Container *con; + E_Dialog *dialog; + int *id; + char text[4096 + PATH_MAX]; + + man = e_manager_current_get(); + if (!man) return; + con = e_container_current_get(man); + if (!con) return; + + id = malloc(sizeof(int)); + *id = pid; + + dialog = e_dialog_new(con, "E", "_fm_error_dialog"); + E_OBJECT(dialog)->data = id; + e_object_del_attach_func_set(E_OBJECT(dialog), _e_fm_error_delete_cb); + e_dialog_button_add(dialog, _("Retry"), NULL, _e_fm_error_retry_cb, NULL); + e_dialog_button_add(dialog, _("Abort"), NULL, _e_fm_error_abort_cb, NULL); + e_dialog_button_add(dialog, _("Ignore this"), NULL, _e_fm_error_ignore_this_cb, NULL); + e_dialog_button_add(dialog, _("Ignore all"), NULL, _e_fm_error_ignore_all_cb, NULL); + + e_dialog_button_focus_num(dialog, 0); + e_dialog_title_set(dialog, _("Error")); + snprintf(text, sizeof(text), + _("An error occured while performing an operation.
" + "%s"), + str); + + e_dialog_text_set(dialog, text); + e_win_centered_set(dialog->win, 1); + e_dialog_show(dialog); +} + +static void +_e_fm_error_delete_cb(void *obj) +{ + int *id = E_OBJECT(obj)->data; + free(id); +} + +static void +_e_fm_send_error_response(int id, E_Fm_Op_Type type) +{ + ecore_ipc_client_send(_e_fm2_client_get()->cl, E_IPC_DOMAIN_FM, 14, + id, 0, 0, + &type, sizeof(E_Fm_Op_Type)); +} + +static void +_e_fm_error_retry_cb(void *data, E_Dialog *dialog) +{ + int *id = E_OBJECT(dialog)->data; + _e_fm_send_error_response(*id, E_FM_OP_ERROR_RESPONSE_RETRY); + e_object_del(E_OBJECT(dialog)); +} + +static void +_e_fm_error_abort_cb(void *data, E_Dialog *dialog) +{ + int *id = E_OBJECT(dialog)->data; + _e_fm_send_error_response(*id, E_FM_OP_ERROR_RESPONSE_ABORT); + e_object_del(E_OBJECT(dialog)); +} + +static void +_e_fm_error_ignore_this_cb(void *data, E_Dialog *dialog) +{ + int *id = E_OBJECT(dialog)->data; + _e_fm_send_error_response(*id, E_FM_OP_ERROR_RESPONSE_IGNORE_THIS); + e_object_del(E_OBJECT(dialog)); +} + +static void +_e_fm_error_ignore_all_cb(void *data, E_Dialog *dialog) +{ + int *id = E_OBJECT(dialog)->data; + _e_fm_send_error_response(*id, E_FM_OP_ERROR_RESPONSE_IGNORE_ALL); + e_object_del(E_OBJECT(dialog)); +} + static void _e_fm2_file_properties(void *data, E_Menu *m, E_Menu_Item *mi) { diff --git a/src/bin/e_fm_main.c b/src/bin/e_fm_main.c index 060012b8d..98cab5198 100644 --- a/src/bin/e_fm_main.c +++ b/src/bin/e_fm_main.c @@ -1,5 +1,5 @@ /* - * vim:ts=8:sw=3:sts=8:noexpandtab:cino=>5n-3f0^-2{2 + * vim:cindent:ts=8:sw=3:sts=8:noexpandtab:cino=>5n-3f0^-2{2 */ #ifndef _FILE_OFFSET_BITS #define _FILE_OFFSET_BITS 64 @@ -35,6 +35,8 @@ #include #include "config.h" +#include "e_fm_op.h" + /* E_DBUS support */ #ifdef HAVE_EDBUS #include @@ -57,6 +59,7 @@ typedef struct _E_Dir E_Dir; typedef struct _E_Fop E_Fop; typedef struct _E_Mod E_Mod; +typedef struct _E_Fm_Slave E_Fm_Slave; struct _E_Dir { @@ -100,12 +103,30 @@ struct _E_Mod unsigned char done : 1; }; +struct _E_Fm_Slave +{ + Ecore_Exe *exe; + int id; +}; + /* local subsystem functions */ static int _e_ipc_init(void); static int _e_ipc_cb_server_add(void *data, int type, void *event); static int _e_ipc_cb_server_del(void *data, int type, void *event); static int _e_ipc_cb_server_data(void *data, int type, void *event); + +static int _e_client_send_overwrite(int id, const char *data, int size); +static int _e_client_send_error(int id, const char *data, int size); +static int _e_client_send_progress(int id, const char *data, int size); + +static int _e_fm_slave_run(E_Fm_Op_Type type, const char *src, const char *dst, int id); +static E_Fm_Slave *_e_fm_slave_get(int id); +static int _e_fm_slave_send(E_Fm_Slave *slave, E_Fm_Op_Type type, void *data, int size); +static int _e_fm_slave_data_cb(void *data, int type, void *event); +static int _e_fm_slave_error_cb(void *data, int type, void *event); +static int _e_fm_slave_del_cb(void *data, int type, void *event); + static void _e_cb_file_monitor(void *data, Ecore_File_Monitor *em, Ecore_File_Event event, const char *path); static int _e_cb_recent_clean(void *data); @@ -125,6 +146,8 @@ static char *_e_str_list_remove(Evas_List **list, char *str); static void _e_path_fix_order(const char *path, const char *rel, int rel_to, int x, int y); static void _e_dir_del(E_Dir *ed); +static const char *_e_prepare_command(E_Fm_Op_Type type, const char *src, const char *dst); + #ifdef HAVE_EDBUS #ifndef EAPI @@ -168,6 +191,8 @@ static Ecore_Ipc_Server *_e_ipc_server = NULL; static Evas_List *_e_dirs = NULL; static Evas_List *_e_fops = NULL; static int _e_sync_num = 0; + +static Evas_List *_e_fm_slaves = NULL; #ifdef HAVE_EDBUS static E_DBus_Connection *_e_dbus_conn = NULL; @@ -213,6 +238,21 @@ main(int argc, char **argv) ecore_file_init(); ecore_ipc_init(); + if (!e_prefix_determine(argv[0])) + { + fprintf(stderr, + "ERROR: Enlightenment cannot determine its installed\n" + " prefix from the system or argv[0].\n" + " This is because it is not on Linux AND has been\n" + " Executed strangely. This is unusual.\n" + ); + e_prefix_fallback(); + } + + ecore_event_handler_add(ECORE_EXE_EVENT_DATA, _e_fm_slave_data_cb, NULL); + ecore_event_handler_add(ECORE_EXE_EVENT_ERROR, _e_fm_slave_error_cb, NULL); + ecore_event_handler_add(ECORE_EXE_EVENT_DEL, _e_fm_slave_del_cb, NULL); + #ifdef HAVE_EDBUS _e_storage_volume_edd_init(); e_dbus_init(); @@ -254,6 +294,8 @@ main(int argc, char **argv) _e_storage_volume_edd_shutdown(); #endif + e_prefix_shutdown(); + ecore_ipc_shutdown(); ecore_file_shutdown(); ecore_string_shutdown(); @@ -1111,16 +1153,7 @@ _e_ipc_cb_server_data(void *data, int type, void *event) break; case 3: /* fop delete file/dir */ { - E_Fop *fop; - - fop = calloc(1, sizeof(E_Fop)); - if (fop) - { - fop->id = e->ref; - fop->src = evas_stringshare_add(e->data); - _e_fops = evas_list_append(_e_fops, fop); - fop->idler = ecore_idler_add(_e_cb_fop_rm_idler, fop); - } + _e_fm_slave_run(E_FM_OP_REMOVE, (const char *)e->data, NULL, e->ref); } break; case 4: /* fop trash file/dir */ @@ -1143,66 +1176,38 @@ _e_ipc_cb_server_data(void *data, int type, void *event) src = e->data; dst = src + strlen(src) + 1; - ecore_file_mv(src, dst); - /* FIXME: send back if succeeded or failed - why */ - _e_path_fix_order(dst, ecore_file_file_get(src), 2, -9999, -9999); + + _e_fm_slave_run(E_FM_OP_MOVE, src, dst, e->ref); } break; case 6: /* fop mv file/dir */ { - E_Fop *fop; - - fop = calloc(1, sizeof(E_Fop)); - if (fop) - { - const char *src, *dst, *rel; - int rel_to, x, y; + const char *src, *dst, *rel; + int rel_to, x, y; - src = e->data; - dst = src + strlen(src) + 1; - rel = dst + strlen(dst) + 1; - memcpy(&rel_to, rel + strlen(rel) + 1, sizeof(int)); - memcpy(&x, rel + strlen(rel) + 1 + sizeof(int), sizeof(int)); - memcpy(&y, rel + strlen(rel) + 1 + sizeof(int), sizeof(int)); - fop->id = e->ref; - fop->src = evas_stringshare_add(src); - fop->dst = evas_stringshare_add(dst); - fop->rel = evas_stringshare_add(rel); - fop->rel_to = rel_to; - fop->x = x; - fop->y = y; - printf("MV %s to %s\n", fop->src, fop->dst); - _e_fops = evas_list_append(_e_fops, fop); - fop->idler = ecore_idler_add(_e_cb_fop_mv_idler, fop); - } + src = e->data; + dst = src + strlen(src) + 1; + rel = dst + strlen(dst) + 1; + memcpy(&rel_to, rel + strlen(rel) + 1, sizeof(int)); + memcpy(&x, rel + strlen(rel) + 1 + sizeof(int), sizeof(int)); + memcpy(&y, rel + strlen(rel) + 1 + sizeof(int), sizeof(int)); + + _e_fm_slave_run(E_FM_OP_MOVE, src, dst, e->ref); } break; case 7: /* fop cp file/dir */ { - E_Fop *fop; - - fop = calloc(1, sizeof(E_Fop)); - if (fop) - { - const char *src, *dst, *rel; - int rel_to, x, y; + const char *src, *dst, *rel; + int rel_to, x, y; - src = e->data; - dst = src + strlen(src) + 1; - rel = dst + strlen(dst) + 1; - memcpy(&rel_to, rel + strlen(rel) + 1, sizeof(int)); - memcpy(&x, rel + strlen(rel) + 1 + sizeof(int), sizeof(int)); - memcpy(&y, rel + strlen(rel) + 1 + sizeof(int), sizeof(int)); - fop->id = e->ref; - fop->src = evas_stringshare_add(src); - fop->dst = evas_stringshare_add(dst); - fop->rel = evas_stringshare_add(rel); - fop->rel_to = rel_to; - fop->x = x; - fop->y = y; - _e_fops = evas_list_append(_e_fops, fop); - fop->idler = ecore_idler_add(_e_cb_fop_cp_idler, fop); - } + src = e->data; + dst = src + strlen(src) + 1; + rel = dst + strlen(dst) + 1; + memcpy(&rel_to, rel + strlen(rel) + 1, sizeof(int)); + memcpy(&x, rel + strlen(rel) + 1 + sizeof(int), sizeof(int)); + memcpy(&y, rel + strlen(rel) + 1 + sizeof(int), sizeof(int)); + + _e_fm_slave_run(E_FM_OP_COPY, src, dst, e->ref); } break; case 8: /* fop mkdir */ @@ -1307,6 +1312,20 @@ _e_ipc_cb_server_data(void *data, int type, void *event) /* FIXME: send back file add if succeeded */ } break; + case 14:/*error response*/ + { + E_Fm_Op_Type type = *(E_Fm_Op_Type *)e->data; + + _e_fm_slave_send(_e_fm_slave_get(e->ref), type, NULL, 0); + } + break; + case 15:/*overwrite response*/ + { + E_Fm_Op_Type type = *(E_Fm_Op_Type *)e->data; + + _e_fm_slave_send(_e_fm_slave_get(e->ref), type, NULL, 0); + } + break; default: break; } @@ -1321,6 +1340,184 @@ _e_ipc_cb_server_data(void *data, int type, void *event) return 1; } +static int _e_client_send_overwrite(int id, const char *data, int size) +{ + return ecore_ipc_server_send(_e_ipc_server, + 6/*E_IPC_DOMAIN_FM*/, + 15/*overwrite*/, + id, 0, 0, data, size); +} + +static int _e_client_send_error(int id, const char *data, int size) +{ + return ecore_ipc_server_send(_e_ipc_server, + 6/*E_IPC_DOMAIN_FM*/, + 14/*error*/, + id, 0, 0, data, size); +} + +static int _e_client_send_progress(int id, const char *data, int size) +{ + return ecore_ipc_server_send(_e_ipc_server, + 6/*E_IPC_DOMAIN_FM*/, + 16/*error*/, + id, 0, 0, data, size); +} +static int _e_fm_slave_run(E_Fm_Op_Type type, const char *src, const char *dst, int id) +{ + E_Fm_Slave *slave; + const char *command; + int result; + + slave = malloc(sizeof(E_Fm_Slave)); + + if(!slave) return 0; + + command = evas_stringshare_add(_e_prepare_command(type, src, dst)); + + slave->id = id; + slave->exe = ecore_exe_pipe_run(command, ECORE_EXE_PIPE_WRITE | ECORE_EXE_PIPE_READ | ECORE_EXE_PIPE_ERROR, slave ); + printf("EFM command: %s\n", command); + + evas_stringshare_del(command); + + _e_fm_slaves = evas_list_append(_e_fm_slaves, slave); + + return (slave->exe != NULL); +} + +static E_Fm_Slave *_e_fm_slave_get(int id) +{ + Evas_List *l = _e_fm_slaves; + E_Fm_Slave *slave; + + while(l) + { + slave = evas_list_data(l); + + if(slave->id == id) + return slave; + + l = evas_list_next(l); + } + + return NULL; +} + +static int _e_fm_slave_send(E_Fm_Slave *slave, E_Fm_Op_Type type, void *data, int size) +{ + void *sdata; + int ssize; + int magic = E_FM_OP_MAGIC; + int result; + + ssize = 3 * sizeof(int) + size; + sdata = malloc(ssize); + + if(!sdata) return 0; + + memcpy(sdata, &magic, sizeof(int)); + memcpy(sdata + sizeof(int), &type, sizeof(E_Fm_Op_Type)); + memcpy(sdata + sizeof(int) + sizeof(E_Fm_Op_Type), &size, sizeof(int)); + + memcpy(sdata + 2 * sizeof(int) + sizeof(E_Fm_Op_Type), data, size); + + result = ecore_exe_send(slave->exe, sdata, ssize); + + free(sdata); + + return result; +} + +static int _e_fm_slave_data_cb(void *data, int type, void *event) +{ + Ecore_Exe_Event_Data *e = event; + E_Fm_Slave *slave; + int magic, id, size; + int response[3]; + void *sdata; + int ssize; + + if(!e) return 1; + + slave = ecore_exe_data_get(e->exe); + + sdata = e->data; + ssize = e->size; + + while(ssize) + { + memcpy(&magic, sdata, sizeof(int)); + memcpy(&id, sdata + sizeof(int), sizeof(int)); + memcpy(&size, sdata + sizeof(int) + sizeof(int), sizeof(int)); + + if(magic != E_FM_OP_MAGIC) + { + DEBUG("%s:%s(%d) Wrong magic number from slave #%d. ", __FILE__, __FUNCTION__, __LINE__, slave->id); + } + + sdata += 3 * sizeof(int); + ssize -= 3 * sizeof(int); + + if(id == E_FM_OP_OVERWRITE) + { + response[0] = E_FM_OP_MAGIC; + response[1] = E_FM_OP_OVERWRITE_RESPONSE_YES; + response[2] = 0; + + _e_client_send_overwrite(slave->id, (const char *)sdata, size); + printf("%s:%s(%d) Overwrite response sent to slave #%d.\n", __FILE__, __FUNCTION__, __LINE__, slave->id); + } + else if(id == E_FM_OP_ERROR) + { + _e_client_send_error(slave->id, (const char *)sdata, size); + printf("%s:%s(%d) Error sent to client from slave #%d.\n", __FILE__, __FUNCTION__, __LINE__, slave->id); + } + else if(id == E_FM_OP_PROGRESS) + { + _e_client_send_progress(slave->id, (const char *)sdata, size); + printf("%s:%s(%d) Progress sent to client from slave #%d.\n", __FILE__, __FUNCTION__, __LINE__, slave->id); + + } + + sdata += size; + ssize -= size; + } + + return 1; +} + +static int _e_fm_slave_error_cb(void *data, int type, void *event) +{ + Ecore_Exe_Event_Data *e = event; + E_Fm_Slave *slave; + + if(e == NULL) return 1; + + slave = ecore_exe_data_get(e->exe); + + printf("EFM: Data from STDERR of slave #%d: %.*s", slave->id, e->size, e->data); + + return 1; +} + +static int _e_fm_slave_del_cb(void *data, int type, void *event) +{ + Ecore_Exe_Event_Del *e = event; + E_Fm_Slave *slave; + + if(!e) return 1; + + slave = ecore_exe_data_get(e->exe); + + if(!slave) return 1; + + _e_fm_slaves = evas_list_remove(_e_fm_slaves, (void *)slave); + free(slave); + + return 1; +} + static void _e_cb_file_monitor(void *data, Ecore_File_Monitor *em, Ecore_File_Event event, const char *path) { @@ -2167,3 +2364,65 @@ _e_dir_del(E_Dir *ed) } free(ed); } + +static void _e_append_char(char **str, int *size, int c) +{ + **str = c; + (*str) ++; + *size++; +} + +static void _e_append_quoted(char **str, int *size, const char *src) +{ + while(*src) + { + if(*src == '\'') + { + _e_append_char(str, size, '\''); + _e_append_char(str, size, '\\'); + _e_append_char(str, size, '\''); + _e_append_char(str, size, '\''); + } + else + _e_append_char(str, size, *src); + + src++; + } +} +/* Returns a string like + * /usr/bin/englightement_op cp 'src' 'dst' + * ready to pass to ecore_exe_pipe_run() + */ + +static const char *_e_prepare_command(E_Fm_Op_Type type, const char *src, const char *dst) +{ + char buffer[PATH_MAX* 3 + 512]; + int length = 0; + char *buf = &buffer[0]; + char command[3]; + + if(type == E_FM_OP_MOVE) + strcpy(command, "mv"); + else if(type == E_FM_OP_REMOVE) + strcpy(command, "rm"); + else + strcpy(command, "cp"); + + length = snprintf(buf, sizeof(buffer), "%s/enlightenment_fm_op %s \'", e_prefix_bin_get(), command); + buf += length; + + _e_append_quoted(&buf, &length, src); + _e_append_char(&buf, &length, '\''); + + if(dst) + { + _e_append_char(&buf, &length, ' '); + _e_append_char(&buf, &length, '\''); + _e_append_quoted(&buf, &length, dst); + _e_append_char(&buf, &length, '\''); + } + + _e_append_char(&buf, &length, '\x00'); + + return strdup(&buffer[0]); +} diff --git a/src/bin/e_fm_op.c b/src/bin/e_fm_op.c new file mode 100644 index 000000000..3eeef86f8 --- /dev/null +++ b/src/bin/e_fm_op.c @@ -0,0 +1,1384 @@ +/* + * vim:cindent:ts=8:sw=3:sts=8:expandtab:cino=>5n-3f0^-2{2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "e_fm_op.h" + +#define READBUFSIZE 65536 +#define COPYBUFSIZE 16384 +#define REMOVECHUNKSIZE 4096 + +#define FREE(p) do { if (p) {free((void *)p); p = NULL;} } while (0) + +typedef struct _E_Fm_Op_Task E_Fm_Op_Task; +typedef struct _E_Fm_Op_Copy_Data E_Fm_Op_Copy_Data; + +static E_Fm_Op_Task *_e_fm_op_task_new(); +static void _e_fm_op_task_free(void *t); + +static void _e_fm_op_remove_link_task(E_Fm_Op_Task *task); +static int _e_fm_op_stdin_data(void *data, Ecore_Fd_Handler * fd_handler); + +static int _e_fm_op_idler_handle_error(int *mark, Evas_List **queue, Evas_List **node, E_Fm_Op_Task *task); + +static int _e_fm_op_work_idler(void *data); +static int _e_fm_op_scan_idler(void *data); + +static void _e_fm_op_send_error(E_Fm_Op_Task * task, E_Fm_Op_Type type, const char *fmt, ...); +static void _e_fm_op_rollback(E_Fm_Op_Task * task); +static void _e_fm_op_update_progress(long long _plus_e_fm_op_done, long long _plus_e_fm_op_total); +static void _e_fm_op_copy_stat_info(E_Fm_Op_Task *task); +static int _e_fm_op_handle_overwrite(E_Fm_Op_Task *task); + +static int _e_fm_op_copy_dir(E_Fm_Op_Task * task); +static int _e_fm_op_copy_link(E_Fm_Op_Task *task); +static int _e_fm_op_copy_fifo(E_Fm_Op_Task *task); +static int _e_fm_op_open_files(E_Fm_Op_Task *task); +static int _e_fm_op_copy_chunk(E_Fm_Op_Task *task); + +static int _e_fm_op_copy_atom(E_Fm_Op_Task * task); +static int _e_fm_op_scan_atom(E_Fm_Op_Task * task); +static int _e_fm_op_copy_stat_info_atom(E_Fm_Op_Task * task); +static int _e_fm_op_remove_atom(E_Fm_Op_Task * task); + +Ecore_Fd_Handler *_e_fm_op_stdin_handler = NULL; + +Evas_List *_e_fm_op_work_queue = NULL, *_e_fm_op_scan_queue = NULL; +Ecore_Idler *_e_fm_op_work_idler_p = NULL, *_e_fm_op_scan_idler_p = NULL; + +long long _e_fm_op_done, _e_fm_op_total; /* Type long long should be 64 bits wide everywhere, + this means that it's max value is 2^63 - 1, which + is 8 388 608 terabytes, and this should be enough. + Well, we'll be multipling _e_fm_op_done by 100, but + still, it is big enough. */ + +int _e_fm_op_abort = 0; /* Abort mark. */ +int _e_fm_op_scan_error = 0; +int _e_fm_op_work_error = 0; +int _e_fm_op_overwrite = 0; + +int _e_fm_op_error_response = E_FM_OP_NONE; +int _e_fm_op_overwrite_response = E_FM_OP_NONE; + +Evas_List *_e_fm_op_separator = NULL; + +void *_e_fm_op_stdin_buffer = NULL; + +struct _E_Fm_Op_Task +{ + struct + { + const char *name; + + struct stat st; + } src; + + struct + { + const char *name; + size_t done; + } dst; + + int started, finished; + + void *data; + + E_Fm_Op_Type type; + E_Fm_Op_Type overwrite; + + Evas_List *link; +}; + +struct _E_Fm_Op_Copy_Data +{ + FILE *from; + FILE *to; +}; + +int +main(int argc, char **argv) +{ + E_Fm_Op_Task *task = NULL; + int i, last; + char *byte = "/"; + char buf[PATH_MAX]; + const char *name = NULL; + E_Fm_Op_Type type; + + ecore_init(); + + _e_fm_op_stdin_buffer = malloc(READBUFSIZE); + + _e_fm_op_stdin_handler = + ecore_main_fd_handler_add(STDIN_FILENO, ECORE_FD_READ, _e_fm_op_stdin_data, NULL, + NULL, NULL); + + if (argc <= 2) + { + return 0; + } + + last = argc - 1; + i = 2; + + if(strcmp(argv[1], "cp") == 0) + { + type = E_FM_OP_COPY; + } + else if(strcmp(argv[1], "mv") == 0) + { + type = E_FM_OP_MOVE; + } + else if(strcmp(argv[1], "rm") == 0) + { + type = E_FM_OP_REMOVE; + } + + if (type == E_FM_OP_COPY || type == E_FM_OP_MOVE) + { + if (argc < 4) + { + return 0; + } + + if(type == E_FM_OP_MOVE) + { + _e_fm_op_work_queue = evas_list_append(_e_fm_op_work_queue, NULL); + _e_fm_op_separator = _e_fm_op_work_queue; + } + + if(argc > 4 && ecore_file_is_dir(argv[last])) + { + if(argv[last][strlen(argv[last] - 1)] == '/') byte = ""; + + while(i < last) + { + name = ecore_file_file_get(argv[i]); + task = _e_fm_op_task_new(); + task->type = type; + task->src.name = evas_stringshare_add(argv[i]); + + snprintf(buf, PATH_MAX, "%s%s%s", argv[last], byte, name); + task->dst.name = evas_stringshare_add(buf); + + if(type == E_FM_OP_MOVE && rename(task->src.name, task->dst.name) == 0) + _e_fm_op_task_free(task); + else + _e_fm_op_scan_queue = evas_list_append(_e_fm_op_scan_queue, task); + + i++; + } + } + else + { + if(type == E_FM_OP_MOVE && rename(argv[2], argv[3]) == 0) + goto quit; + + task = _e_fm_op_task_new(); + task->type = type; + task->src.name = evas_stringshare_add(argv[2]); + task->dst.name = evas_stringshare_add(argv[3]); + + _e_fm_op_scan_queue = evas_list_append(_e_fm_op_scan_queue, task); + } + } + else if (type == E_FM_OP_REMOVE) + { + if (argc < 3) + { + return 0; + } + + while(i <= last) + { + task = _e_fm_op_task_new(); + task->type = type; + task->src.name = evas_stringshare_add(argv[i]); + + _e_fm_op_scan_queue = evas_list_append(_e_fm_op_scan_queue, task); + + i++; + } + } + + _e_fm_op_scan_idler_p = ecore_idler_add(_e_fm_op_scan_idler, NULL); + _e_fm_op_work_idler_p = ecore_idler_add(_e_fm_op_work_idler, NULL); + + ecore_main_loop_begin(); + +quit: + ecore_shutdown(); + + free(_e_fm_op_stdin_buffer); + + E_FM_OP_DEBUG("Slave quit.\n"); + + return 0; +} + +/* Create new task. */ + +static E_Fm_Op_Task *_e_fm_op_task_new() +{ + E_Fm_Op_Task *t = malloc(sizeof(E_Fm_Op_Task)); + + t->src.name = NULL; + memset(&(t->src.st), 0, sizeof(struct stat)); + + t->dst.name = NULL; + t->dst.done = 0; + + t->started = 0; + t->finished = 0; + + t->data = NULL; + + t->type = E_FM_OP_NONE; + t->overwrite = E_FM_OP_NONE; + + t->link = NULL; + + return t; +} + +/* Free task. */ + +static void +_e_fm_op_task_free(void *t) +{ + E_Fm_Op_Task *task = t; + E_Fm_Op_Copy_Data *data; + + if (!task) + return; + + if (task->src.name) + evas_stringshare_del(task->src.name); + if (task->dst.name) + evas_stringshare_del(task->dst.name); + + if (task->data) + { + data = task->data; + + if(task->type == E_FM_OP_COPY) + { + if(data->from) + { + fclose(data->from); + } + + if(data->to) + { + fclose(data->to); + } + } + FREE(task->data); + } + + FREE(task); +} + +/* Removes link task from work queue. + * Link task is not NULL in case of MOVE. Then two tasks are created: copy and remove. + * Remove task is a link task for the copy task. If copy task is aborted (e.g. error + * occured and user chooses to ignore this), then the remove task is removed from + * queue with this functions. + */ + +static void _e_fm_op_remove_link_task(E_Fm_Op_Task *task) +{ + if(task->link) + { + _e_fm_op_work_queue = evas_list_remove_list(_e_fm_op_work_queue, task->link); + _e_fm_op_task_free(task->link); + task->link = NULL; + } +} + +/* + * Handles data from STDIN. + * Received data must be in this format: + * 1) (int) magic number, + * 2) (int) id, + * 3) (int) message length. + * Right now message length is always 0. Id is what matters. + * + * This function uses a couple of static variables and a global + * variable _e_fm_op_stdin_buffer to deal with a situation, when read() + * did not actually read enough data. + */ + +static int +_e_fm_op_stdin_data(void *data, Ecore_Fd_Handler * fd_handler) +{ + int fd = ecore_main_fd_handler_fd_get(fd_handler); + static void *buf = NULL; + static int length = 0; + void *begin = NULL; + ssize_t num = 0; + int msize; + int identity; + + if(!buf) + { + buf = _e_fm_op_stdin_buffer; + length = 0; + } + + num = read(fd, buf, READBUFSIZE - length); + + if (num == 0) + { + E_FM_OP_DEBUG("STDIN was closed. Abort. \n"); + _e_fm_op_abort = 1; + } + else if (num < 0) + { + E_FM_OP_DEBUG("Error while reading from STDIN: read returned -1. (%s) Abort. \n", strerror(errno)); + _e_fm_op_abort = 1; + } + else + { + length += num; + + buf = _e_fm_op_stdin_buffer; + begin = _e_fm_op_stdin_buffer; + + while (length >= 3 * sizeof(int)) + { + begin = buf; + + /* Check magic. */ + if (*(int *)buf != E_FM_OP_MAGIC) + { + E_FM_OP_DEBUG("Error while reading from STDIN: magic is not correct!\n"); + break; + } + buf += sizeof(int); + + /* Read indentifying data. */ + memcpy(&identity, buf, sizeof(int)); + buf += sizeof(int); + + /* Read message length. */ + memcpy(&msize, buf, sizeof(int)); + buf += sizeof(int); + + if (length - 3*sizeof(int) < msize) + { + /* There is not enough data to read the whole message. */ + break; + } + + length -= 3*sizeof(int); + + /* You may want to read msize bytes of data too, + * but currently commands here do not have any data. + * msize is always 0. + */ + + switch (identity) + { + case E_FM_OP_ABORT: + _e_fm_op_abort = 1; + E_FM_OP_DEBUG("Aborted.\n"); + break; + + case E_FM_OP_ERROR_RESPONSE_ABORT: + case E_FM_OP_ERROR_RESPONSE_IGNORE_THIS: + case E_FM_OP_ERROR_RESPONSE_IGNORE_ALL: + case E_FM_OP_ERROR_RESPONSE_RETRY: + _e_fm_op_error_response = identity; + break; + + case E_FM_OP_OVERWRITE_RESPONSE_NO: + case E_FM_OP_OVERWRITE_RESPONSE_NO_ALL: + case E_FM_OP_OVERWRITE_RESPONSE_YES: + case E_FM_OP_OVERWRITE_RESPONSE_YES_ALL: + _e_fm_op_overwrite_response = identity; + E_FM_OP_DEBUG("Overwrite response set.\n"); + break; + } + } + + if(length > 0) + { + memmove(_e_fm_op_stdin_buffer, begin, length); + } + + buf = _e_fm_op_stdin_buffer + length; + } + + return 1; +} + +#define _E_FM_OP_ERROR_SEND_SCAN(_task, _e_fm_op_error_type, _fmt, ...)\ + do\ + {\ + int _errno = errno;\ + _e_fm_op_scan_error = 1;\ + _e_fm_op_send_error(_task, _e_fm_op_error_type, _fmt, __VA_ARGS__, strerror(_errno));\ + return 1;\ + }\ + while(0) + +#define _E_FM_OP_ERROR_SEND_WORK(_task, _e_fm_op_error_type, _fmt, ...)\ + do\ + {\ + int _errno = errno;\ + _e_fm_op_work_error = 1;\ + _e_fm_op_send_error(_task, _e_fm_op_error_type, _fmt, __VA_ARGS__, strerror(_errno));\ + return 1;\ + }\ + while(0) + +/* Code to deal with overwrites and errors in idlers. + * Basically, it checks if we got a response. + * Returns 1 if we did; otherwise checks it and does what needs to be done. + */ + +static int _e_fm_op_idler_handle_error(int *mark, Evas_List **queue, Evas_List **node, E_Fm_Op_Task *task) +{ + if(_e_fm_op_overwrite) + { + if(_e_fm_op_overwrite_response != E_FM_OP_NONE) + { + task->overwrite = _e_fm_op_overwrite_response; + _e_fm_op_work_error = 0; + _e_fm_op_scan_error = 0; + } + else + { + return 1; + } + } + else if(*mark) + { + if (_e_fm_op_error_response == E_FM_OP_NONE) + { + /* No response yet. */ + return 1; + } + else + { + E_FM_OP_DEBUG("Got response.\n"); + /* Got response. */ + if (_e_fm_op_error_response == E_FM_OP_ERROR_RESPONSE_ABORT) + { + /* Mark as abort. */ + _e_fm_op_abort = 1; + _e_fm_op_error_response = E_FM_OP_NONE; + _e_fm_op_rollback(task); + } + else if (_e_fm_op_error_response == E_FM_OP_ERROR_RESPONSE_RETRY) + { + *mark = 0; + _e_fm_op_error_response = E_FM_OP_NONE; + } + else if (_e_fm_op_error_response == E_FM_OP_ERROR_RESPONSE_IGNORE_THIS) + { + _e_fm_op_rollback(task); + _e_fm_op_remove_link_task(task); + *queue = evas_list_remove_list(*queue, *node); + _e_fm_op_error_response = E_FM_OP_NONE; + *mark = 0; + *node = NULL; + return 1; + } + else if (_e_fm_op_error_response == E_FM_OP_ERROR_RESPONSE_IGNORE_ALL) + { + E_FM_OP_DEBUG("E_Fm_Op_Task '%s' --> '%s' was automatically aborted.\n", + task->src.name, task->dst.name); + _e_fm_op_rollback(task); + _e_fm_op_remove_link_task(task); + *queue = evas_list_remove_list(*queue, *node); + *node = NULL; + *mark = 0; + /* Do not clean out _e_fm_op_error_response. This way when another error occures, it would be handled automatically. */ + return 1; + } + } + } + else if( _e_fm_op_work_error || _e_fm_op_scan_error) + { + return 1; + } + return 0; +} +/* This works very simple. Take a task from queue and run appropriate _atom() on it. + * If after _atom() is done, task->finished is 1 remove the task from queue. Otherwise, + * run _atom() on the same task on next call. + * + * If we have an abort (_e_fm_op_abort = 1), then _atom() should recognize it and do smth. + * After this, just finish everything. + */ + +static int +_e_fm_op_work_idler(void *data) +{ + /* E_Fm_Op_Task is marked static here because _e_fm_op_work_queue can be populated with another + * tasks between calls. So it is possible when a part of file is copied and then + * another task is pushed into _e_fm_op_work_queue and the new one if performed while + * the first one is not finished yet. This is not cool, so we make sure one task + * is performed until it is finished. Well, this can be an issue with removing + * directories. For example, if we are trying to remove a non-empty directory, + * then this will go into infinite loop. But this should never happen. + * + * BTW, the same is propably right for the _e_fm_op_scan_idler(). + */ + static Evas_List *node = NULL; + E_Fm_Op_Task *task = NULL; + + if(!node) node = _e_fm_op_work_queue; + + task = evas_list_data(node); + + if(!task) + { + node = _e_fm_op_work_queue; + task = evas_list_data(node); + } + + if(!task) + { + if( _e_fm_op_separator && _e_fm_op_work_queue == _e_fm_op_separator && _e_fm_op_scan_idler_p == NULL) + { + /* You may want to look at the comment in _e_fm_op_scan_atom() about this separator thing. */ + _e_fm_op_work_queue = evas_list_remove_list(_e_fm_op_work_queue, _e_fm_op_separator); + node = NULL; + return 1; + } + + if (_e_fm_op_scan_idler_p == NULL) + { + ecore_main_loop_quit(); + } + + return 1; + } + + if(_e_fm_op_idler_handle_error(&_e_fm_op_work_error, &_e_fm_op_work_queue, &node, task)) return 1; + + task->started = 1; + + if(task->type == E_FM_OP_COPY) + { + _e_fm_op_copy_atom(task); + } + else if(task->type == E_FM_OP_REMOVE) + { + _e_fm_op_remove_atom(task); + } + else if(task->type == E_FM_OP_COPY_STAT_INFO) + { + _e_fm_op_copy_stat_info_atom(task); + } + + if (task->finished) + { + _e_fm_op_work_queue = evas_list_remove_list(_e_fm_op_work_queue, node); + _e_fm_op_task_free(task); + node = NULL; + } + + if (_e_fm_op_abort) + { + /* So, _atom did what it whats in case of abort. Now to idler. */ + ecore_main_loop_quit(); + return 0; + } + + + return 1; +} + +/* This works pretty much the same as _e_fm_op_work_idler(), except that + * if this is a dir, then look into its contents and create a task + * for those files. And we don't have _e_fm_op_separator here. + */ + +int +_e_fm_op_scan_idler(void *data) +{ + static Evas_List *node = NULL; + E_Fm_Op_Task *task = NULL; + char buf[PATH_MAX]; + static struct dirent *de = NULL; + static DIR *dir = NULL; + E_Fm_Op_Task *ntask = NULL; + + if(!node) node = _e_fm_op_scan_queue; + + task = evas_list_data(node); + + if(!task) + { + node = _e_fm_op_scan_queue; + task = evas_list_data(node); + } + + if (!task) + { + _e_fm_op_scan_idler_p = NULL; + return 0; + } + + if (_e_fm_op_abort) + { + /* We're marked for abortion. */ + ecore_main_loop_quit(); + return 0; + } + + if(_e_fm_op_idler_handle_error(&_e_fm_op_scan_error, &_e_fm_op_scan_queue, &node, task)) return 1; + + if (task->type == E_FM_OP_COPY_STAT_INFO) + { + _e_fm_op_scan_atom(task); + if (task->finished) + { + _e_fm_op_scan_queue = evas_list_remove_list(_e_fm_op_scan_queue, node); + _e_fm_op_task_free(task); + node = NULL; + } + } + else if (!dir && !task->started) + { + if (lstat(task->src.name, &(task->src.st)) < 0) + { + _E_FM_OP_ERROR_SEND_SCAN(task, E_FM_OP_ERROR, "Cannot lstat '%s': %s.", task->src.name); + } + + if (S_ISDIR(task->src.st.st_mode)) + { + /* If it's a dir, then look through it and add a task for each. */ + + dir = opendir(task->src.name); + if (!dir) + { + _E_FM_OP_ERROR_SEND_SCAN(task, E_FM_OP_ERROR, "Cannot open directory '%s': %s.", task->dst.name); + } + } + else + task->started = 1; + } + else if(!task->started) + { + de = readdir(dir); + + if(!de) + { + ntask = _e_fm_op_task_new(); + + ntask->type = E_FM_OP_COPY_STAT_INFO; + + ntask->src.name = evas_stringshare_add(task->src.name); + memcpy(&(ntask->src.st), &(task->src.st), sizeof(struct stat)); + + if (task->dst.name) + { + ntask->dst.name = evas_stringshare_add(task->dst.name); + } + else + { + ntask->dst.name = NULL; + } + + if(task->type == E_FM_OP_REMOVE) + _e_fm_op_scan_queue = evas_list_prepend(_e_fm_op_scan_queue, ntask); + else + _e_fm_op_scan_queue = evas_list_append(_e_fm_op_scan_queue, ntask); + + task->started = 1; + closedir(dir); + dir = NULL; + node = NULL; + return 1; + } + + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + { + return 1; + } + + ntask = _e_fm_op_task_new(); + + ntask->type = task->type; + + snprintf(buf, sizeof(buf), "%s/%s", task->src.name, + de->d_name); + ntask->src.name = evas_stringshare_add(buf); + + if (task->dst.name) + { + snprintf(buf, sizeof(buf), "%s/%s", task->dst.name, + de->d_name); + ntask->dst.name = evas_stringshare_add(buf); + } + else + { + ntask->dst.name = NULL; + } + + if(task->type == E_FM_OP_REMOVE) + _e_fm_op_scan_queue = evas_list_prepend(_e_fm_op_scan_queue, ntask); + else + _e_fm_op_scan_queue = evas_list_append(_e_fm_op_scan_queue, ntask); + } + else + { + _e_fm_op_scan_atom(task); + if (task->finished) + { + _e_fm_op_scan_queue = evas_list_remove_list(_e_fm_op_scan_queue, node); + _e_fm_op_task_free(task); + node = NULL; + } + } + + return 1; +} + +/* Packs and sends an error to STDOUT. + * type is either E_FM_OP_ERROR or E_FM_OP_OVERWRITE. + * fmt is a printf format string, the other arguments + * are for this format string, + */ + +static void +_e_fm_op_send_error(E_Fm_Op_Task * task, E_Fm_Op_Type type, const char *fmt, ...) +{ + va_list ap; + char buffer[READBUFSIZE]; + void *buf = &buffer[0]; + char *str = buf + 3 * sizeof(int); + int len = 0; + + va_start(ap, fmt); + + if (_e_fm_op_error_response == E_FM_OP_ERROR_RESPONSE_IGNORE_ALL) + { + /* Do nothing. */ + } + else + { + vsnprintf(str, READBUFSIZE - 3 * sizeof(int), fmt, ap); + len = strlen(str); + + *(int *)buf = E_FM_OP_MAGIC; + *(int *)(buf + sizeof(int)) = type; + *(int *)(buf + 2 * sizeof(int)) = len + 1; + + write(STDOUT_FILENO, buf, 3*sizeof(int) + len + 1); + + E_FM_OP_DEBUG(str); + E_FM_OP_DEBUG(" Error sent.\n"); + } + + va_end(ap); +} + +/* Unrolls task: makes a clean up and updates progress info. + */ + +static void +_e_fm_op_rollback(E_Fm_Op_Task * task) +{ + E_Fm_Op_Copy_Data *data; + + if (!task) + return; + + if (task->type == E_FM_OP_COPY) + { + data = task->data; + + if (data) + { + if (data->from) + { + fclose(data->from); + data->from = NULL; + } + if (data->to) + { + fclose(data->to); + data->to = NULL; + } + } + + FREE(task->data); + } + + if(task->type == E_FM_OP_COPY) + _e_fm_op_update_progress(-task->dst.done, -task->src.st.st_size); + else + _e_fm_op_update_progress(-REMOVECHUNKSIZE, -REMOVECHUNKSIZE); +} + +/* Updates progress. + * _plus_data is how much more works is done and _plus_e_fm_op_total + * is how much more work we found out needs to be done + * (it is not zero primarily in _e_fm_op_scan_idler()) + * + * It calculates progress in percent. And once a second calculates eta. + * If either of them changes from their previuos values, then the are + * packed and written to STDOUT. + */ + +static void +_e_fm_op_update_progress(long long _plus_e_fm_op_done, long long _plus_e_fm_op_total) +{ + static int ppercent = -1; + int percent; + + static double ctime = 0; + static double stime = 0; + double eta = 0; + static int peta = -1; + + int data[5]; + + _e_fm_op_done += _plus_e_fm_op_done; + _e_fm_op_total += _plus_e_fm_op_total; + + if(_e_fm_op_scan_idler_p) return; /* Do not send progress until scan is done.*/ + + if(_e_fm_op_total != 0) + { + percent = _e_fm_op_done * 100 / _e_fm_op_total % 101; /* % 101 is for the case when somehow work queue works faster than scan queue. _e_fm_op_done * 100 should not cause arithmetic overflow, since long long can hold really big values. */ + + eta = peta; + + if(!stime) stime = ecore_time_get(); + + if(_e_fm_op_done && ecore_time_get() - ctime > 1.0 ) /* Update ETA once a second */ + { + ctime = ecore_time_get(); + eta = (ctime - stime) * (_e_fm_op_total - _e_fm_op_done) / _e_fm_op_done; + eta = (int) (eta + 0.5); + } + + if(percent != ppercent || eta != peta) + { + ppercent = percent; + peta = eta; + + data[0] = E_FM_OP_MAGIC; + data[1] = E_FM_OP_PROGRESS; + data[2] = 2*sizeof(int); + data[3] = percent; + data[4] = peta; + + write(STDOUT_FILENO, &data[0], 5*sizeof(int)); + + E_FM_OP_DEBUG("Time left: %d at %e\n", peta, ctime - stime); + E_FM_OP_DEBUG("Progress %d. \n", percent); + } + } +} + +/* We just use this code in several places. + */ + +static void +_e_fm_op_copy_stat_info(E_Fm_Op_Task *task) +{ + struct utimbuf ut; + + if(!task->dst.name) return; + + chmod(task->dst.name, task->src.st.st_mode); + chown(task->dst.name, task->src.st.st_uid, + task->src.st.st_gid); + ut.actime = task->src.st.st_atime; + ut.modtime = task->src.st.st_mtime; + utime(task->dst.name, &ut); +} + +static int +_e_fm_op_handle_overwrite(E_Fm_Op_Task *task) +{ + struct stat st; + + if(task->overwrite == E_FM_OP_OVERWRITE_RESPONSE_YES_ALL + || _e_fm_op_overwrite_response == E_FM_OP_OVERWRITE_RESPONSE_YES_ALL) + { + _e_fm_op_overwrite = 0; + return 0; + } + else if(task->overwrite == E_FM_OP_OVERWRITE_RESPONSE_YES + || _e_fm_op_overwrite_response == E_FM_OP_OVERWRITE_RESPONSE_YES) + { + _e_fm_op_overwrite_response = E_FM_OP_NONE; + _e_fm_op_overwrite = 0; + return 0; + } + else if(task->overwrite == E_FM_OP_OVERWRITE_RESPONSE_NO + || _e_fm_op_overwrite_response == E_FM_OP_OVERWRITE_RESPONSE_NO) + { + task->finished = 1; + _e_fm_op_rollback(task); + _e_fm_op_remove_link_task(task); + _e_fm_op_overwrite_response = E_FM_OP_NONE; + _e_fm_op_overwrite = 0; + return 1; + } + else if(task->overwrite == E_FM_OP_OVERWRITE_RESPONSE_NO_ALL + || _e_fm_op_overwrite_response == E_FM_OP_OVERWRITE_RESPONSE_NO_ALL) + { + task->finished = 1; + _e_fm_op_rollback(task); + _e_fm_op_remove_link_task(task); + _e_fm_op_overwrite = 0; + return 1; + } + + if( stat(task->dst.name, &st) == 0) + { + /* File exists. */ + if( _e_fm_op_overwrite_response == E_FM_OP_OVERWRITE_RESPONSE_NO_ALL) + { + task->finished = 1; + _e_fm_op_rollback(task); + return 1; + } + else + { + _e_fm_op_overwrite = 1; + _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_OVERWRITE, "File '%s' already exists. Overwrite?", task->dst.name); + } + } + + return 0; +} + +static int +_e_fm_op_copy_dir(E_Fm_Op_Task * task) +{ + struct stat st; + /* Directory. Just create one in destatation. */ + if (mkdir + (task->dst.name, + S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | + S_IXOTH) == -1) + { + if (errno == EEXIST) + { + if (lstat(task->dst.name, &st) < 0) + _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot lstat '%s': %s.", task->dst.name); + if (!S_ISDIR(st.st_mode)) + { + /* Let's try to delete the file and create a dir */ + if(unlink(task->dst.name) == -1) + _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot unlink '%s': %s.", task->dst.name); + if(mkdir(task->dst.name, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == -1) + _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot make directory '%s': %s.", task->dst.name); + } + } + else + { + _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot make directory '%s': %s.", task->dst.name); + } + } + + task->dst.done += task->src.st.st_size; + _e_fm_op_update_progress(task->src.st.st_size, 0); + + /* Finish with this task. */ + task->finished = 1; + + return 0; +} + +static int +_e_fm_op_copy_link(E_Fm_Op_Task *task) +{ + size_t len; + char path[PATH_MAX]; + + len = readlink(task->src.name, &path[0], PATH_MAX); + path[len] = 0; + + if(symlink(path, task->dst.name) != 0) + { + if(errno == EEXIST) + { + if(unlink(task->dst.name) == -1) + _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot unlink '%s': %s.", task->dst.name); + if(symlink(path, task->dst.name) == -1) + _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot create link from '%s' to '%s': %s.", path, task->dst.name); + } + else + { + _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot create link from '%s' to '%s': %s.", path, task->dst.name); + } + } + + task->dst.done += task->src.st.st_size; + _e_fm_op_update_progress(task->src.st.st_size, 0); + + _e_fm_op_copy_stat_info(task); + + task->finished = 1; + + return 0; +} + +static int +_e_fm_op_copy_fifo(E_Fm_Op_Task *task) +{ + if(mkfifo(task->dst.name, task->src.st.st_mode) == -1) + { + if(errno == EEXIST) + { + if(unlink(task->dst.name) == -1) + _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot unlink '%s': %s.", task->dst.name); + if(mkfifo(task->dst.name, task->src.st.st_mode) == -1) + _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot make FIFO at '%s': %s.", task->dst.name); + } + else + { + _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot make FIFO at '%s': %s.", task->dst.name); + } + } + + _e_fm_op_copy_stat_info(task); + + task->dst.done += task->src.st.st_size; + _e_fm_op_update_progress(task->src.st.st_size, 0); + + task->finished = 1; + + return 0; +} + +static int +_e_fm_op_open_files(E_Fm_Op_Task *task) +{ + E_Fm_Op_Copy_Data *data = task->data; + + /* Ordinary file. */ + if(!data) + { + data = malloc(sizeof(E_Fm_Op_Copy_Data)); + task->data = data; + data->to = NULL; + data->from = NULL; + } + + if(!data->from) + { + data->from = fopen(task->src.name, "rb"); + if (data->from == NULL) + { + _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot open file '%s' for reading: %s.", task->src.name); + } + } + + if(!data->to) + { + data->to = fopen(task->dst.name, "wb"); + if (data->to == NULL) + { + _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot open file '%s' for writing: %s.", task->dst.name); + } + + } + + return 0; +} + +static int +_e_fm_op_copy_chunk(E_Fm_Op_Task *task) +{ + E_Fm_Op_Copy_Data *data; + size_t dread, dwrite; + char buf[COPYBUFSIZE]; + + data = task->data; + + if (_e_fm_op_abort) + { + _e_fm_op_rollback(task); + + task->finished = 1; + return 1; + } + + dread = fread(buf, 1, sizeof(buf), data->from); + if (dread <= 0) + { + if (!feof(data->from)) + { + _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot read data from '%s': %s.", task->dst.name); + } + + fclose(data->from); + fclose(data->to); + data->from = NULL; + data->from = NULL; + + _e_fm_op_copy_stat_info(task); + + FREE(task->data); + + task->finished = 1; + + _e_fm_op_update_progress(0, 0); + + return 1; + } + + dwrite = fwrite(buf, 1, dread, data->to); + + if (dwrite < dread) + { + _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot write data to '%s': %s.", task->dst.name); + } + + task->dst.done += dread; + _e_fm_op_update_progress(dwrite, 0); + + return 0; +} +/* + * _e_fm_op_copy_atom(), _e_fm_op_remove_atom() and _e_fm_op_scan_atom() are functions that + * perform very small operations. + * + * _e_fm_op_copy_atom(), for example, makes one of three things: + * 1) opens files for writing and reading. This may take several calls -- until overwrite issues are not resolved. + * 2) reads some bytes from one file and writes them to the other. + * 3) closes both files if there is nothing more to read. + * + * _e_fm_op_remove_atom() removes smth. + * + * _e_fm_op_scan_atom() pushes new tasks for the _e_fm_op_work_idler(). One task for cp&rm and two tasks for mv. + * + * _e_fm_op_copy_atom() and _e_fm_op_remove_atom() are called from _e_fm_op_work_idler(). + * _e_fm_op_scan_atom() is called from _e_fm_op_scan_idler(). + * + * These functions are called repeatedly until they put task->finished = 1. After that the task is removed from queue. + * + * Return value does not matter. It's there only to _E_FM_OP_ERROR_SEND macro to work correctly. (Well, it works fine, just don't want GCC to produce a warning.) + */ + +static int +_e_fm_op_copy_atom(E_Fm_Op_Task * task) +{ + E_Fm_Op_Copy_Data *data; + + if (!task) + { + return 1; + } + + data = task->data; + + if (!data || !data->to || !data->from) /* Did not touch the files yet. */ + { + E_FM_OP_DEBUG("Copy: %s --> %s\n", task->src.name, task->dst.name); + + if (_e_fm_op_abort) + { + /* We're marked for abortion. Don't do anything. + * Just return -- abort gets handled in _idler. + */ + task->finished = 1; + return 1; + } + + if(_e_fm_op_handle_overwrite(task)) return 1; + + if (S_ISDIR(task->src.st.st_mode)) + { + if(_e_fm_op_copy_dir(task)) return 1; + } + else if (S_ISLNK(task->src.st.st_mode)) + { + if(_e_fm_op_copy_link(task)) return 1; + } + else if (S_ISFIFO(task->src.st.st_mode)) + { + if(_e_fm_op_copy_fifo(task)) return 1; + } + else if (S_ISREG(task->src.st.st_mode)) + { + if(_e_fm_op_open_files(task)) return 1; + } + } + else + { + if(_e_fm_op_copy_chunk(task)) return 1; + } + + return 1; +} + +static int +_e_fm_op_scan_atom(E_Fm_Op_Task * task) +{ + E_Fm_Op_Task *ctask, *rtask; + + if (!task) + { /* Error. */ + return 1; + } + + task->finished = 1; + + /* Now push a new task to the work idler. */ + + if(task->type == E_FM_OP_COPY) + { + _e_fm_op_update_progress(0, task->src.st.st_size); + + ctask = _e_fm_op_task_new(); + ctask->src.name = evas_stringshare_add(task->src.name); + memcpy(&(ctask->src.st), &(task->src.st), sizeof(struct stat)); + if (task->dst.name) + ctask->dst.name = evas_stringshare_add(task->dst.name); + ctask->type = E_FM_OP_COPY; + + _e_fm_op_work_queue = evas_list_append(_e_fm_op_work_queue, ctask); + } + else if(task->type == E_FM_OP_COPY_STAT_INFO) + { + _e_fm_op_update_progress(0, REMOVECHUNKSIZE); + + ctask = _e_fm_op_task_new(); + ctask->src.name = evas_stringshare_add(task->src.name); + memcpy(&(ctask->src.st), &(task->src.st), sizeof(struct stat)); + if (task->dst.name) + ctask->dst.name = evas_stringshare_add(task->dst.name); + ctask->type = E_FM_OP_COPY_STAT_INFO; + + _e_fm_op_work_queue = evas_list_append(_e_fm_op_work_queue, ctask); + } + else if(task->type == E_FM_OP_REMOVE) + { + _e_fm_op_update_progress(0, REMOVECHUNKSIZE); + + rtask = _e_fm_op_task_new(); + rtask->src.name = evas_stringshare_add(task->src.name); + memcpy(&(rtask->src.st), &(task->src.st), sizeof(struct stat)); + if (task->dst.name) + rtask->dst.name = evas_stringshare_add(task->dst.name); + rtask->type = E_FM_OP_REMOVE; + + _e_fm_op_work_queue = evas_list_prepend(_e_fm_op_work_queue, rtask); + + } + else if(task->type == E_FM_OP_MOVE) + { + /* Copy task. */ + _e_fm_op_update_progress(0, task->src.st.st_size); + ctask = _e_fm_op_task_new(); + + ctask->src.name = evas_stringshare_add(task->src.name); + memcpy(&(ctask->src.st), &(task->src.st), sizeof(struct stat)); + if (task->dst.name) + ctask->dst.name = evas_stringshare_add(task->dst.name); + ctask->type = E_FM_OP_COPY; + + _e_fm_op_work_queue = evas_list_prepend(_e_fm_op_work_queue, ctask); + + /* Remove task. */ + _e_fm_op_update_progress(0, REMOVECHUNKSIZE); + rtask = _e_fm_op_task_new(); + + rtask->src.name = evas_stringshare_add(task->src.name); + memcpy(&(rtask->src.st), &(task->src.st), sizeof(struct stat)); + if (task->dst.name) + rtask->dst.name = evas_stringshare_add(task->dst.name); + rtask->type = E_FM_OP_REMOVE; + + /* We put remove task after the separator. Work idler won't go + * there unless scan is done and this means that all tasks for + * copy are already in queue. And they will be performed before + * the delete tasks. + * + * If we don't use this separator trick, then there easily can be + * a situation when remove task is performed before all files are + * copied. + */ + + _e_fm_op_work_queue = evas_list_append_relative_list(_e_fm_op_work_queue, rtask, _e_fm_op_separator); + + ctask->link = _e_fm_op_separator->next; + } + + return 1; +} + +static int +_e_fm_op_copy_stat_info_atom(E_Fm_Op_Task * task) +{ + E_FM_OP_DEBUG("Stat: %s --> %s\n", task->src.name, task->dst.name); + + _e_fm_op_copy_stat_info(task); + task->finished = 1; + task->dst.done += REMOVECHUNKSIZE; + + _e_fm_op_update_progress(REMOVECHUNKSIZE, 0); + + return 0; +} + +static int +_e_fm_op_remove_atom(E_Fm_Op_Task * task) +{ + if (_e_fm_op_abort) + { + return 1; + } + + E_FM_OP_DEBUG("Remove: %s\n", task->src.name); + + if (S_ISDIR(task->src.st.st_mode)) + { + if (rmdir(task->src.name) == -1) + { + if (errno == ENOTEMPTY) + { + E_FM_OP_DEBUG("Attempt to remove non-empty directory.\n"); + /* This should never happen due to way tasks are added to the work queue. If this happens (for example new files were created after the scan was complete), implicitly delete everything. */ + ecore_file_recursive_rm(task->src.name); + task->finished = 1; /* Make sure that task is removed. */ + return 1; + } + else + { + _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot remove directory '%s': %s.", task->src.name); + } + } + } + else if (unlink(task->src.name) == -1) + { + _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot remove file '%s': %s.", task->src.name); + } + + task->dst.done += REMOVECHUNKSIZE; + _e_fm_op_update_progress(REMOVECHUNKSIZE, 0); + + task->finished = 1; + + return 1; +} diff --git a/src/bin/e_fm_op.h b/src/bin/e_fm_op.h new file mode 100644 index 000000000..adabc2bbd --- /dev/null +++ b/src/bin/e_fm_op.h @@ -0,0 +1,32 @@ +/* + * vim:cindent:ts=8:sw=3:sts=8:expandtab:cino=>5n-3f0^-2{2 + */ +#ifndef _E_FM_OP_H +#define _E_FM_OP_H + +#define E_FM_OP_DEBUG(...) fprintf(stderr, __VA_ARGS__) + +#define E_FM_OP_MAGIC 314 + +typedef enum _E_Fm_Op_Type +{ + E_FM_OP_COPY = 0, + E_FM_OP_MOVE = 1, + E_FM_OP_REMOVE = 2, + E_FM_OP_ABORT = 3, + E_FM_OP_ERROR = 4, + E_FM_OP_ERROR_RESPONSE_IGNORE_THIS = 5, + E_FM_OP_ERROR_RESPONSE_IGNORE_ALL = 6, + E_FM_OP_ERROR_RESPONSE_ABORT = 7, + E_FM_OP_PROGRESS = 8, + E_FM_OP_NONE = 9, + E_FM_OP_ERROR_RESPONSE_RETRY = 10, + E_FM_OP_OVERWRITE = 11, + E_FM_OP_OVERWRITE_RESPONSE_NO = 12, + E_FM_OP_OVERWRITE_RESPONSE_NO_ALL = 13, + E_FM_OP_OVERWRITE_RESPONSE_YES = 14, + E_FM_OP_OVERWRITE_RESPONSE_YES_ALL = 15, + E_FM_OP_COPY_STAT_INFO = 16 +} E_Fm_Op_Type; + +#endif