File Manager / src / model /
fs-operation.c
/*
* Copyright 2014 - 2015 Samsung Electronics Co., Ltd All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "model/fs-operation.h"
#include "utils/common-utils.h"
#include "utils/model-utils.h"
#include "utils/logger.h"
#include <sys/types.h>
#include <dirent.h>
#include <Ecore.h>
#define FM_MAX_INT_DIGITS_COUNT 10
#define FM_COPY_BUF_SIZE 16384
struct _fs_operation {
Eina_List *source_list;
char *dst_path;
operation_type oper_type;
fs_operation_cb_func cb_func;
fs_operation_cb_data *cb_data;
Ecore_Thread *exec_thread;
Eina_Bool is_canceled;
};
typedef struct _fs_operation_copy_info {
char *source_path;
char *destination_path;
file_type type;
} fs_operation_copy_info;
static int _fs_operation_copy(fs_operation *operation);
static int _fs_operation_move(fs_operation *operation);
static int _fs_operation_delete(fs_operation *operation);
static int _fs_operation_rename(fs_operation *operation, const char *source_path, const char *dest_path);
static void _fs_operation_clear_data(fs_operation *operation);
static void _fs_operation_clear_copy_list(Eina_List **copy_list);
static int _fs_operation_recursive_fill_copy_list(fs_operation *operation, Eina_List *src_list, Eina_List **copy_list,
const char *destination_path);
static int _fs_operation_proceed_copy(Eina_List *copy_list);
static bool _fs_operation_file_copy(const char *src, const char *dst);
static bool _fs_operation_file_recursive_rm(const char *dir);
static int _fs_operation_get_items_list(Eina_List **list_to_fill, const char *source_path);
static char *_fs_operation_remove_file_extention(const char *path);
static char *_fs_operation_append_filename_if_duplicate(const char *path_to_check, const char *original_name);
static void _fs_operation_delete_exec_thread(fs_operation *operation);
static void _fs_operation_run(void *data, Ecore_Thread *thread);
static void _fs_operation_end(void *data, Ecore_Thread *thread);
fs_operation *fs_operation_create()
{
fs_operation *operation = calloc(1, sizeof(fs_operation));
if (operation) {
operation->oper_type = OPERATION_TYPE_NONE;
operation->is_canceled = EINA_FALSE;
}
return operation;
}
void fs_operation_destroy(fs_operation *operation)
{
if (operation) {
if (operation->exec_thread && !operation->is_canceled) {
operation->is_canceled = EINA_TRUE;
return;
}
_fs_operation_delete_exec_thread(operation);
_fs_operation_clear_data(operation);
free(operation);
}
}
int fs_operation_set_data(fs_operation *operation,
Eina_List *file_list,
const char *dst_path,
operation_type type)
{
RETVM_IF(!operation, RESULT_TYPE_INVALID_ARG, "Operation object is NULL");
RETVM_IF(!file_list, RESULT_TYPE_INVALID_ARG, "File list is NULL");
RETVM_IF(!dst_path && (type != OPERATION_TYPE_DELETE), RESULT_TYPE_INVALID_ARG, "Destination path is NULL");
RETVM_IF(type == OPERATION_TYPE_NONE, RESULT_TYPE_INVALID_ARG, "No appropriate operation type");
int res = common_util_copy_selected_file_list(file_list, &operation->source_list);
if (res != RESULT_TYPE_OK) {
_fs_operation_clear_data(operation);
ERR("Failed to copy source list");
return res;
}
if (dst_path) {
operation->dst_path = strdup(dst_path);
if (!operation->dst_path) {
_fs_operation_clear_data(operation);
ERR("Failed to allocate memory for destination path");
return RESULT_TYPE_FAIL_ALLOCATE_MEMORY;
}
}
operation->oper_type = type;
return RESULT_TYPE_OK;
}
void _fs_operation_delete_exec_thread(fs_operation *operation)
{
if (operation->exec_thread) {
ecore_thread_cancel(operation->exec_thread);
operation->exec_thread = NULL;
}
}
void _fs_operation_run(void *data, Ecore_Thread *thread)
{
fs_operation *operation = data;
int res = RESULT_TYPE_FAIL;
switch (operation->oper_type) {
case OPERATION_TYPE_COPY:
res = _fs_operation_copy(operation);
break;
case OPERATION_TYPE_MOVE:
res = _fs_operation_move(operation);
break;
case OPERATION_TYPE_DELETE:
res = _fs_operation_delete(operation);
break;
default:
ERR("Operation type not set");
return;
break;
}
if (operation->is_canceled) {
return;
}
if (operation->cb_data) {
operation->cb_data->result = res;
}
}
void _fs_operation_end(void *data, Ecore_Thread *thread)
{
fs_operation *operation = data;
_fs_operation_delete_exec_thread(operation);
if (operation->is_canceled) {
fs_operation_destroy(operation);
return;
}
if (operation->cb_func) {
operation->cb_func(operation->cb_data);
}
}
int fs_operation_execute(fs_operation *operation,
fs_operation_cb_func cb_func,
fs_operation_cb_data *cb_data)
{
RETVM_IF(!operation, RESULT_TYPE_INVALID_ARG, "Operation object is NULL");
RETVM_IF(!operation->source_list, RESULT_TYPE_FAIL, "File list not set");
RETVM_IF(operation->oper_type == OPERATION_TYPE_NONE, RESULT_TYPE_FAIL, "Type of operation not set");
RETVM_IF(!operation->dst_path && (operation->oper_type != OPERATION_TYPE_DELETE),
RESULT_TYPE_FAIL, "Destination path not set");
operation->cb_func = cb_func;
operation->cb_data = cb_data;
operation->exec_thread = ecore_thread_feedback_run(_fs_operation_run,
NULL,
_fs_operation_end,
NULL,
(void *)operation,
EINA_TRUE);
RETVM_IF(!operation->exec_thread, RESULT_TYPE_FAIL, "Failed to create thread");
return RESULT_TYPE_OK;
}
static void _fs_operation_clear_data(fs_operation *operation)
{
common_util_clear_file_list(&operation->source_list);
operation->oper_type = OPERATION_TYPE_NONE;
operation->cb_func = NULL;
operation->cb_data = NULL;
free(operation->dst_path);
operation->dst_path = NULL;
}
static bool _fs_operation_file_copy(const char *src, const char *dst)
{
FILE *source_file = NULL;
FILE *destintation_file = NULL;
char buf[FM_COPY_BUF_SIZE] = {0};
char src_realpath[PATH_MAX] = {'\0'};
char dst_realpath[PATH_MAX] = {'\0'};
size_t num = 0;
if (!realpath(src, src_realpath)) {
return false;
}
if (realpath(dst, dst_realpath) && !strcmp(src_realpath, dst_realpath)) {
return false;
}
source_file = fopen(src, "rb");
if (!source_file) {
return false;
}
destintation_file = fopen(dst, "wb");
if (!destintation_file) {
fclose(source_file);
return false;
}
while ((num = fread(buf, 1, sizeof(buf), source_file)) > 0) {
if (fwrite(buf, 1, num, destintation_file) != num) {
fclose(source_file);
fclose(destintation_file);
return false;
}
}
fclose(source_file);
fclose(destintation_file);
return true;
}
static bool _fs_operation_file_recursive_rm(const char *dir)
{
DIR *dir_ptr = NULL;
struct dirent *dp = NULL;
char path[PATH_MAX] = {'\0'};
struct stat st;
if (stat(dir, &st) == -1) {
return false;
}
if (S_ISDIR(st.st_mode)) {
dir_ptr = opendir(dir);
if (dir_ptr) {
while ((dp = readdir(dir_ptr))) {
if ((strcmp(dp->d_name, ".")) && (strcmp(dp->d_name, ".."))) {
snprintf(path, PATH_MAX, "%s/%s", dir, dp->d_name);
if (!_fs_operation_file_recursive_rm(path)) {
closedir(dir_ptr);
return false;
}
}
}
closedir(dir_ptr);
}
}
if (remove(dir) < 0) {
return false;
}
return true;
}
static void _fs_operation_clear_copy_list(Eina_List **copy_list)
{
RETM_IF(!copy_list, "Copy list pointer is NULL");
if (*copy_list) {
fs_operation_copy_info *pNode = NULL;
EINA_LIST_FREE(*copy_list, pNode)
{
free(pNode->source_path);
free(pNode->destination_path);
free(pNode);
}
*copy_list = NULL;
}
}
static char *_fs_operation_remove_file_extention(const char *path)
{
int len = strlen(path);
char *striped_path = calloc(1, (len + 1));
char *extention = strrchr(path, '.');
if (!extention) {
strncpy(striped_path, path, (len + 1));
return striped_path;
}
strncpy(striped_path, path, (extention - path));
striped_path[extention - path] = '\0';
return striped_path;
}
static char *_fs_operation_append_filename_if_duplicate(const char *path_to_check, const char *striped_path)
{
if (model_utils_is_file_exists(path_to_check)) {
int copy_index = 1;
char *new_path = NULL;
char index_buffer[FM_MAX_INT_DIGITS_COUNT] = { 0 };
char *extention = NULL;
Eina_Bool is_dir = model_utils_file_is_dir(path_to_check);
if (!is_dir) {
extention = strrchr(path_to_check, '.');
}
while (true) {
sprintf(index_buffer, "%d", copy_index);
new_path = common_util_strconcat(striped_path, "(", index_buffer, ")", extention, NULL);
if (model_utils_is_file_exists(new_path)) {
free(new_path);
copy_index++;
} else {
return new_path;
}
}
}
return strdup(path_to_check);
}
static int _fs_operation_get_items_list(Eina_List **list_to_fill, const char *source_path)
{
Eina_List *dir_list = NULL;
Eina_List *file_list = NULL;
int res = model_utils_read_dir(source_path, &dir_list, &file_list);
RETVM_IF(res != RESULT_TYPE_OK, res, "Failed to get items list");
*list_to_fill = eina_list_merge(dir_list, file_list);
return RESULT_TYPE_OK;
}
static int _fs_operation_recursive_fill_copy_list(fs_operation *operation, Eina_List *src_list,
Eina_List **copy_list,
const char *destination_path)
{
Eina_List *list = NULL;
node_info *data = NULL;
EINA_LIST_FOREACH(src_list, list, data) {
if (operation->is_canceled) {
return RESULT_TYPE_OPERATION_INTERUPTED;
}
char *striped_path = NULL;
char *source_path = common_util_strconcat(data->parent_path, "/", data->name, NULL);
char *dest_path = common_util_strconcat(destination_path, "/", data->name, NULL);
if (strcmp(source_path, destination_path) == 0) {
free(source_path);
free(dest_path);
return RESULT_TYPE_OPERATION_INVALID_DEST;
}
if (model_utils_file_is_dir(dest_path)) {
striped_path = strdup(dest_path);
} else {
striped_path = _fs_operation_remove_file_extention(dest_path);
}
char *checked_path = _fs_operation_append_filename_if_duplicate(dest_path, striped_path);
int res = RESULT_TYPE_OK;
fs_operation_copy_info *copy_item = calloc(1, sizeof(fs_operation_copy_info));
copy_item->source_path = source_path;
copy_item->destination_path = checked_path;
copy_item->type = data->type;
if (data->type == FILE_TYPE_DIR) {
Eina_List *new_src_list = NULL;
res = _fs_operation_get_items_list(&new_src_list, source_path);
if (res == RESULT_TYPE_OK) {
res = _fs_operation_recursive_fill_copy_list(operation, new_src_list, copy_list, checked_path);
common_util_clear_file_list(&new_src_list);
}
}
*copy_list = eina_list_append(*copy_list, copy_item);
free(dest_path);
free(striped_path);
RETVM_IF(res != RESULT_TYPE_OK, res, "Failed to copy items");
}
return RESULT_TYPE_OK;
}
static int _fs_operation_proceed_copy(Eina_List *copy_list)
{
Eina_List *list = NULL;
fs_operation_copy_info *data = NULL;
int res = RESULT_TYPE_OK;
EINA_LIST_REVERSE_FOREACH(copy_list, list, data) {
if (data->type == FILE_TYPE_DIR) {
(void)mkdir(data->destination_path, DIR_MODE);
} else if (!_fs_operation_file_copy(data->source_path, data->destination_path)) {
res = RESULT_TYPE_FAIL;
}
}
RETVM_IF(res != RESULT_TYPE_OK, res, "Failed to proceed copy list");
return RESULT_TYPE_OK;
}
static int _fs_operation_copy(fs_operation *operation)
{
Eina_List *copy_list = NULL;
int res = _fs_operation_recursive_fill_copy_list(operation, operation->source_list, ©_list, operation->dst_path);
if (res == RESULT_TYPE_OK) {
res = _fs_operation_proceed_copy(copy_list);
}
_fs_operation_clear_copy_list(©_list);
RETVM_IF(res != RESULT_TYPE_OK, res, "Failed to copy source");
return RESULT_TYPE_OK;
}
static int _fs_operation_rename(fs_operation *operation, const char *source_path, const char *dest_path)
{
int res = RESULT_TYPE_FAIL;
struct stat st;
if (stat(source_path, &st) != 0) {
ERR("Failed to get stat for folder %s. ERRNO = %d", source_path, errno);
return res;
}
if (S_ISREG(st.st_mode)) {
if (!_fs_operation_file_copy(source_path, dest_path)) {
ERR("Failed to copy file %s", source_path);
return res;
}
chmod(dest_path, st.st_mode);
int res_remove = remove(source_path);
RETVM_IF(res_remove < 0, RESULT_TYPE_FAIL, "Failed to remove file %s", source_path);
res = RESULT_TYPE_OK;
} else {
Eina_List *copy_list = NULL;
Eina_List *src_list = NULL;
res = _fs_operation_get_items_list(&src_list, source_path);
RETVM_IF(res != RESULT_TYPE_OK, res, "Failed to get item list for dis %s", source_path);
res = _fs_operation_recursive_fill_copy_list(operation, src_list, ©_list, dest_path);
common_util_clear_file_list(&src_list);
RETVM_IF(res != RESULT_TYPE_OK, res, "Failed to fill copy list recursivly");
fs_operation_copy_info *copy_item = calloc(1, sizeof(fs_operation_copy_info));
copy_item->source_path = strdup(source_path);
copy_item->destination_path = strdup(dest_path);
copy_item->type = FILE_TYPE_DIR;
copy_list = eina_list_append(copy_list, copy_item);
res = _fs_operation_proceed_copy(copy_list);
_fs_operation_clear_copy_list(©_list);
RETVM_IF(res != RESULT_TYPE_OK, res, "Failed perform operation copy");
if (!_fs_operation_file_recursive_rm(source_path)) {
ERR("Failed to delete dir %s recursively", source_path);
return RESULT_TYPE_FAIL;
}
}
return res;
}
static int _fs_operation_move(fs_operation *operation)
{
Eina_List *list = NULL;
node_info *data = NULL;
int result = RESULT_TYPE_OK;
EINA_LIST_FOREACH(operation->source_list, list, data) {
if (operation->is_canceled) {
return RESULT_TYPE_OPERATION_INTERUPTED;
}
char *striped_path = NULL;
char *source_path = common_util_strconcat(data->parent_path, "/", data->name, NULL);
char *dest_path = common_util_strconcat(operation->dst_path, "/", data->name, NULL);
if (strcmp(source_path, dest_path) == 0) {
free(source_path);
free(dest_path);
return RESULT_TYPE_OPERATION_INVALID_DEST;
}
if (model_utils_file_is_dir(dest_path)) {
striped_path = strdup(dest_path);
} else {
striped_path = _fs_operation_remove_file_extention(dest_path);
}
char *checked_path = _fs_operation_append_filename_if_duplicate(dest_path, striped_path);
if (rename(source_path, checked_path)) {
if (errno == EINVAL) {
result = RESULT_TYPE_OPERATION_INVALID_DEST;
} else if (errno == EXDEV) {
result = _fs_operation_rename(operation, source_path, checked_path);
}
}
free(dest_path);
free(striped_path);
free(source_path);
free(checked_path);
RETVM_IF(result != RESULT_TYPE_OK, result, "Failed to move source ");
}
return RESULT_TYPE_OK;
}
static int _fs_operation_delete(fs_operation *operation)
{
Eina_List *list = NULL;
node_info *data = NULL;
int result = RESULT_TYPE_OK;
EINA_LIST_FOREACH(operation->source_list, list, data) {
if (operation->is_canceled) {
result = RESULT_TYPE_OPERATION_INTERUPTED;
break;
}
char *temp_name = common_util_strconcat(data->parent_path, "/", data->name, NULL);
if (data->type == FILE_TYPE_DIR && model_utils_file_is_dir(temp_name)) {
if (!_fs_operation_file_recursive_rm(temp_name)) {
ERR("Failed to delete dir %s recursively", temp_name);
}
} else if (model_utils_is_file_exists(temp_name)) {
if (remove(temp_name) < 0) {
ERR("Failed to delete file %s", temp_name);
}
} else {
ERR("%s %s doesn't exist", (data->type == FILE_TYPE_DIR) ? "Dir" : "File", temp_name);
}
free(temp_name);
}
return result;
}