src/cmd/fsexam/src/fsexam-convname.c
author yz157939@agc105
Fri, 25 Apr 2008 17:02:23 +0800
changeset 147 8c4ef02c14b8
permissions -rw-r--r--
replace new version of fsexam, old version 0.3.1 is not used any more

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <strings.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <libgen.h>
#include <unistd.h>

#include "fsexam-header.h"
#include "fsexam-convcontent.h"

/* Append SUFFIX when has duplicated name */
#define SUFFIX  "utf-8"

static gboolean write_to_disk (FSEXAM_setting *,
                               const gchar *path,
                               const gchar *origname,
                               const gchar *utf8name,
                               short from_encoding,
                               short to_encoding,
                               gchar **actualname);

static gboolean real_convert (FSEXAM_setting *setting, 
                              const gchar *dname, 
                              const gchar *bname, 
                              gchar **newname);
static gboolean convert_symlink_target_node (
                              GNode *node, 
                              FSEXAM_setting *setting, 
                              gboolean restore);
static gboolean wrapped_convert_node (GNode *node, FSEXAM_setting *setting);
static gboolean wrapped_restore_convert_node (GNode *node, gpointer data);
static gboolean convert_node (GNode *node, gpointer data);
static gboolean dryrun_convert_node (GNode *node, gpointer data);
static gboolean restore_convert_node (GNode *node, gpointer data);
static gboolean fsexam_convert_scenario_for_name (FSEXAM_setting *setting);
static gboolean fsexam_restore_name (FSEXAM_setting *setting, GList *list);
static gboolean _convert_directory (FSEXAM_setting *setting, const gchar *);
static void     convert_directory (FSEXAM_setting *setting, GNode *node);
static gint     construct_candidate_list (GList *list, gboolean forname);


/*
 *  Recreate symlink from file in symlink_node to file in target_node
 */
static gboolean
relink_symlink (GNode *symlink_node, 
                GNode *target_node, 
                FSEXAM_setting *setting)
{
    gchar     *symlink_contents = NULL;
    gchar     *symlink_path = NULL;
    gchar     *target_path = NULL;
    gchar     *log_msg = NULL;
    gboolean  ret = FALSE;

    if ((NULL == symlink_node) || (NULL == target_node) || (NULL == setting))
        return FALSE;

    symlink_path = fsexam_tree_get_path (symlink_node, FALSE);
    symlink_contents = g_file_read_link (symlink_path, NULL);
    
    if (NULL == symlink_contents) {
        fsexam_errno = ERR_CANNOT_READ;
        goto done;
    }

    if (remove (symlink_path) == -1) {  /* delete original symlink */
        fsexam_errno = ERR_CANNOT_RM_SYMLINK;
        goto done;
    }

    if (*symlink_contents == '/') {     /* absolute symlink */
        target_path = fsexam_tree_get_path (target_node, FALSE);
    }else{
        /* reconstruct symlink path for relative symlink */
        gchar     *tmp = symlink_contents;
        gchar     *cur = tmp;
        GNode     *parent_node = symlink_node->parent;
        gboolean  finish = FALSE;

        while (!finish) {
            if ((*tmp != '/') && (*tmp != '\0')) {
                tmp++;
                continue;
            }else{                      /* got one subpath */
                if (*tmp == '\0')
                    finish = TRUE;
                else
                    *tmp = '\0';

                if (strcmp (cur, ".") == 0) {
                    /* no op */
                }else if (strcmp (cur, "..") == 0) {
                    parent_node = parent_node->parent;
                }else if (strcmp (cur, "") == 0) {
                    /* this means '//' in the symlink_contents */
                    cur = "/";      
                }else{
                    GNode *gnode = fsexam_tree_search_name (parent_node, cur);
                    if (gnode == NULL) {  /* fallback to use absolute path */
                        g_free (target_path);
                        target_path = fsexam_tree_get_path (target_node, FALSE);

                        break;
                    }else{
                        TreeNode *tnode = (TreeNode *)gnode->data;

                        if (TREENODE_IS_CONVERTED (tnode)) 
                            cur = TREENODE_GET_UTF8 (tnode);
                        else
                            cur = TREENODE_GET_ORIG (tnode);
                    }
                    
                    parent_node = parent_node->parent;
                }
               
                if (target_path == NULL) {
                    target_path = g_strdup (cur);
                }else{
                    if (strcmp (cur, "/") == 0) {
                        cur = g_strdup_printf ("%s%s", target_path, cur);
                    }else{
                        cur = g_strdup_printf ("%s/%s", target_path, cur);
                    }

                    g_free (target_path);
                    target_path = cur;
                }

                cur = ++tmp;
            }
        } /* endof while */
    }

    if (symlink (target_path, symlink_path) == -1) {
        fsexam_errno = ERR_LOST_SYMLINK_FILE;
        goto done;
    }

    ret = TRUE;

done:
    if (ret)    
        log_msg = _("Re-create symbolic link success"); 

    fsexam_log_puts (setting->log_info, symlink_path, log_msg);
    if (setting->display_msg)
        setting->display_msg (symlink_path, log_msg);

    g_free (symlink_contents);
    g_free (target_path);
    g_free (symlink_path);

    return ret;
}

/*====================================================================
 *  Function Name:  write_to_disk
 *
 *  Parameters:
 *      FSEXAM_setting *setting:
 *      gchar *path:    The dir name which contain file to be renamed
 *      gchar *orig:    The original name
 *      gchar *utf8name:    The utf8 name
 *      gchar **actualname: the actual name used if succeed
 *
 *  Desc:
 *      Rename the file from orig to utf8name. If newname exists, then will 
 *      find one new name, and actualname point to it.
 *
 *      Will handle log/history in this function, dryrun/restore
 *      may call this function directly.
 *
 *  Return value:
 *      If rename success, then return TRUE; 
 *      otherwise(such as can't rename, can't write to log) return FALSE.
 *
 *  Author:     Yandong Yao 2006/08/31
 ========================================================================*/ 
static gboolean
write_to_disk (FSEXAM_setting *setting, 
               const gchar *path,      /* the path of current file */
               const gchar *origname,  /* old base name            */
               const gchar *utf8name,  /* new base name            */
               short from_encoding,    /* from encoding            */
               short to_encoding,      /* to encoding              */
               gchar **actualname)      /* return the actual used   */
{
    static gchar    oldname[PATH_MAX];
    static gchar    newname[PATH_MAX];
    gchar           *retname = NULL;
    gchar           *fname = NULL;
    gchar           *msg = NULL;
    gboolean        ret = FALSE;

    if ((NULL == setting) || (NULL == path) || (NULL == origname) 
            || (NULL == utf8name))
        return FALSE;

    fsexam_errno = ERR_OK;

    /* construct full old name and full new name */
    g_snprintf (oldname, PATH_MAX - 1, "%s/%s", path, origname);
    g_snprintf (newname, PATH_MAX - 1, "%s/%s", path, utf8name);

    /* whether newname == oldname? */
    if (strcmp (utf8name, origname) == 0) {
        fsexam_errno = ERR_NAME_SAME;
        goto done;
    }

    if (g_file_test (newname, G_FILE_TEST_EXISTS)) {
        fsexam_errno = ERR_NAME_EXIST;
        fname = find_non_exist_name (newname);

        if (fname == NULL) {
            fsexam_errno = ERR_CANNOT_RENAME;
        }else if (rename(oldname, fname) == 0){
            retname = g_strdup (basename (fname));
            ret = TRUE;
        }else{
            fsexam_errno = ERR_CANNOT_RENAME;
        }
    }else{
        if (rename(oldname, newname) == 0){
            retname = g_strdup (utf8name);
            ret = TRUE;
        }else{
            fsexam_errno = ERR_CANNOT_RENAME; /* inc dup name */
        }
    }

    if (ret) { 
        if (! (setting->flags & FSEXAM_SETTING_FLAGS_UNDO)) {
            gboolean same_serial = TRUE;

            if (setting->flags & FSEXAM_SETTING_FLAGS_DIFF_SERIAL) {
                setting->flags &= ~FSEXAM_SETTING_FLAGS_DIFF_SERIAL;
                same_serial = FALSE;
            }

            fsexam_history_put (setting->hist_info, 
                                ConvName, 
                                newname,
                                from_encoding, 
                                to_encoding, 
                                same_serial);
        }

        if (setting->update_gui) {
            setting->update_gui (setting, path, origname, retname);
        }
        
        ++setting->succ_num;
    } else {
        ++setting->fail_num;
    }
       
done:
    msg = g_strdup_printf (_("[Name] %s -> %s"), 
                        id2encoding (from_encoding), 
                        id2encoding (to_encoding));

    /* Display messages to user */
    if (setting->display_msg) {
        if (ret) 
            setting->display_msg (newname, msg);
        else
            setting->display_msg (oldname, fsexam_error_get_msg ());
    }

    /* Writting log */
    if (fsexam_errno == ERR_OK) {
        fsexam_log_puts (setting->log_info, newname, msg);
    }else if (fsexam_errno == ERR_NAME_EXIST) {
        fsexam_log_puts (setting->log_info, fname, NULL);
    }else{
        fsexam_log_puts (setting->log_info, newname, NULL);
    }
    
    if (actualname != NULL)
        *actualname = retname;  //freed outside
    else
        g_free (retname);

    g_free (msg);
    g_free (fname);

    return ret;
}

/*====================================================================
 *  Function Name:  real_convert
 *
 *  Parameters:
 *      FSEXAM_setting *setting: 
 *      const gchar *dname: the directory in which file reside
 *      const gchar *bname: the filename without any directory name
 *      gchar **newname: return the new name when success. Need free by caller
 *
 *  Desc:
 *      real_convert() will convert the filename using provided encoding list,
 *      and display candidates to user in interactive mode.
 *
 *      If success to rename file on the disk, then newname will contain the 
 *      new name.
 *
 *      This function will handle log information also.
 *
 *      write_to_disk() will handle log either, because it can be call by
 *      other funcs.
 *
 *  Return value:
 *      If succeed to rename the file, return TRUE, otherwise return FALSE.
 *      In dryrun mode, always return FALSE.
 *
 *      The most important usage for the return value is to determine whether
 *      fsexam change the real file name on disk or not.
 *
 *  Author:     Yandong Yao 2006/09/01
 ========================================================================*/ 
static gboolean
real_convert (FSEXAM_setting *setting, 
              const gchar *dname, 
              const gchar *bname, 
              gchar **newname)
{
    Score       score;
    gchar       *fullname = NULL;
    gboolean    ret = FALSE;

    if ((NULL == setting) || (NULL == dname) || (NULL ==bname))
        return FALSE;

    fsexam_errno = ERR_OK; 

    fullname = g_strdup_printf ("%s/%s", dname, bname);

    if (setting->pref->auto_detect) { /* handle encoding auto detection */
        GList *detected_encoding;

        detected_encoding = str_encoding_detect (bname, DEFAULT_DETECTING_FLAG);
        setting->pref->encode_list = fsexam_encoding_add_auto (
                                                setting->pref->encode_list, 
                                                detected_encoding);
        auto_encoding_free (detected_encoding);
    }

    score = fsexam_encoding_decode (setting->pref->encode_list, 
                                    ConvName, 
                                    (gchar *)bname, 
                                    strlen(bname), 
                                    setting->pref->force);


    if (setting->pref->dry_run){    /* dry run */
        ret = fsexam_dryrun_puts (setting->dryrun_info, 
                                  fullname, 
                                  score, 
                                  setting->pref->encode_list, 
                                  ConvName);    
        ret ? ++setting->succ_num : ++setting->fail_num;
    } else {                        /* real convert */  
        gint        index = 0;
        gchar       *actualname = NULL;
        Encoding    *encoding = NULL;

        if ((score == FAIL) || (score == ORIGINAL)){
            fsexam_errno = (score == FAIL) ? ERR_NO_PROPER_ENCODING 
                                           : ERR_NAME_UTF8_ALREADY;
            fsexam_log_puts_folder_and_name (setting->log_info, 
                                             dname, bname, 
                                             NULL);
            if (setting->display_msg)
                setting->display_msg (fullname, fsexam_error_get_msg());

            goto done;
        }   
       
	    /* 
	     * User may select don't ask me again
	     */ 
	    if (setting->gold_index != -1) {
            index = setting->gold_index;
	    } else if (setting->pref->auto_conversion) {
            index = fsexam_encoding_get_first_index (
                                        setting->pref->encode_list);
        } else {
            index = setting->get_index (setting->pref->encode_list, TRUE);
        }

        if (index == -1) {          /* cancel the selection */
            fsexam_errno = ERR_CANCEL_CONVERSION;
            goto done;
        }

        encoding = (Encoding *)g_list_nth_data (setting->pref->encode_list, 
                                                index);
        if (NULL == encoding){
            fsexam_errno = ERR_ENCODING_INDEX_INVALID;
            fsexam_log_puts_folder_and_name (setting->log_info, 
                                             dname, bname, 
                                             NULL);
            if (setting->display_msg)
                setting->display_msg (fullname, fsexam_error_get_msg());

            goto done;
        }

        ret = write_to_disk (setting, 
                             dname, 
                             bname, 
                             encoding->u.converted_text, 
                             encoding->encodingID, 
                             encoding2id ("UTF-8"),
                             &actualname);

        if ((ret) && (newname)) {   // Return new name
            *newname = actualname;
        }else{
            g_free (actualname);
        }
    }
    
done:
    if (setting->pref->auto_detect)
        setting->pref->encode_list = fsexam_encoding_remove_auto (
                                                setting->pref->encode_list);

    g_free (fullname);

    return ret;
}

/*
 * convert one directory recursively. won't care about symlink
 */
static gboolean
common_convert_directory (FSEXAM_setting *setting, 
                          const gchar *dname, 
                          gboolean restore)
{
    struct stat     statbuf;
    struct dirent   *dirp = NULL;
    DIR             *dp = NULL;
    gchar           *childname = NULL;
    gboolean        ret = FALSE;

    if ((NULL == dname) || (NULL == setting))
        return FALSE;

    if ((dp = opendir (dname)) == NULL){
        fsexam_errno = ERR_CANNOT_OPEN;
        fsexam_log_puts (setting->log_info, dname, NULL);
            if (setting->display_msg)
                setting->display_msg (dname, fsexam_error_get_msg());

        return ret;
    }

    while ((dirp = readdir (dp)) != NULL){
        if ((strcmp (dirp->d_name, ".") == 0)
                || (strcmp (dirp->d_name, "..") == 0))
            continue;

        fsexam_errno = ERR_OK;

        childname = g_strdup_printf ("%s/%s", dname, dirp->d_name);

        /* need convert this filename or not? */
        if (lstat (childname, &statbuf) == -1) 
            fsexam_errno = ERR_FILE_NONEXIST;
        else if (!(S_ISREG(statbuf.st_mode)) 
                && !(S_ISDIR(statbuf.st_mode)) && !(S_ISLNK(statbuf.st_mode))){
            fsexam_errno = ERR_FILE_TYPE_NOT_SUPPORT; 
        }else if ((! setting->pref->hidden) && (*(dirp->d_name) == '.')){
            fsexam_errno = ERR_IGNORE_HIDDEN_FILE;
        }else if ((!setting->pref->remote) 
                && (is_remote_file (setting->remote_path, childname))) {
            fsexam_errno = ERR_IGNORE_REMOTE_FILE;
        } 

        if (fsexam_errno != ERR_OK) {
            fsexam_log_puts (setting->log_info, childname, NULL);
            if (setting->display_msg)
                setting->display_msg (childname, fsexam_error_get_msg());
            g_free (childname);
            continue;
        }
    
        if ((!setting->pref->force) 
                && (str_isutf8 (dirp->d_name, DEFAULT_DETECTING_FLAG))) {
            fsexam_errno = ERR_NAME_UTF8_ALREADY;
        }

        if (restore) {
            //Currently tree for restore name has no -R node, so no-op
        }else if (fsexam_errno != ERR_NAME_UTF8_ALREADY) {
            gchar  *newname = NULL;

            ret = real_convert (setting, dname, dirp->d_name, &newname);

            if (ret && newname != NULL){
                g_free (childname);
                childname = g_strdup_printf ("%s/%s", dname, newname);
                lstat(childname, &statbuf); 
            }
                
            g_free (newname);
        }

        if (setting->flags & FSEXAM_SETTING_FLAGS_STOP) {
            g_free (childname);
            goto done;
        }

        if (! setting->pref->dry_run 
                && setting->pref->special 
                && fsexam_special_is_special_for_name (childname, setting)) {
            gchar *hist_search_path;

            /* history search path is for search Hist_item in history file */
            hist_search_path = g_strdup_printf ("%s/%s", dname, dirp->d_name);
            fsexam_compress_convert_name (setting, 
                                          childname, 
                                          hist_search_path, 
                                          restore);
            g_free (hist_search_path);
        } else if (S_ISDIR(statbuf.st_mode)) {
            _convert_directory(setting, childname);
        }

        g_free (childname);
    }

done:
    closedir (dp);

    return ret;
}

static gboolean
_convert_directory (FSEXAM_setting *setting, const gchar *dname)
{
    return common_convert_directory (setting, dname, FALSE);
}

/*
static gboolean
_restore_directory (FSEXAM_setting *setting, const gchar *dname)
{
    return common_convert_directory (setting, dname, TRUE);
}
*/

/*
 *  convert to fullpath, then call _convert_directory
 */
static void
convert_directory (FSEXAM_setting *setting, GNode *node)
{
    gchar *dir = fsexam_tree_get_path (node, FALSE); 

    _convert_directory (setting, dir);

    return;
}

/*
 * Convert the symlink target, and convert parent first if has not do.
 */
static gboolean
convert_symlink_target_node (GNode *node, 
                             FSEXAM_setting *setting, 
                             gboolean restore)
{
    TreeNode    *tnode = NULL;

    if ((NULL == node) || (NULL == setting))
        return FALSE;
    
    tnode = (TreeNode *)node->data;

    if (TREENODE_IS_TRAVERSED (tnode)) {
        return TREENODE_IS_CONVERTED (tnode) ? TRUE : FALSE;
    }

    if (setting->flags & FSEXAM_SETTING_FLAGS_STOP)
        return FALSE;    /* stop the recursive */

    if ((node->parent != NULL) 
        && (! TREENODE_IS_TRAVERSED ((TreeNode *)((node->parent)->data)))) {
        convert_symlink_target_node (node->parent, setting, restore);
    }

    if (setting->flags & FSEXAM_SETTING_FLAGS_STOP)
        return FALSE;    /* stop the recursive */

    if (restore)
        return wrapped_restore_convert_node (node, (gpointer)setting);
    else
        return wrapped_convert_node (node, (gpointer)setting);
}

/*============================================================================
 * Function Name:   convert_node
 *
 * Parameters:
 *      FSEXAM_setting *:   contain preference information
 *      GNode *node:        The node which will be handled
 *
 * Desc:
 *      GNode Traverse function. Convert one node in the whole tree. 
 *      Need care about recursive flag
 *
 *      Because this function will called by g_node_traverse, so we need 
 *      handle various error information here.
 *
 * Return value:
 *      Return TRUE to stop g_node_traverse().
 *
 * Author:
 *      Yandong Yao 2006/08/22
 ============================================================================*/
static gboolean 
convert_node (GNode *node, gpointer data)
{
    FSEXAM_setting  *setting = (FSEXAM_setting *)data;

    wrapped_convert_node (node, setting);

    if (setting->flags & FSEXAM_SETTING_FLAGS_STOP)
        return TRUE;

    return FALSE;
}

/*
 *  Return TRUE if current node has been converted.
 */
static gboolean
wrapped_convert_node (GNode *node, FSEXAM_setting *setting)
{
    TreeNode        *tnode = node->data;
    gboolean        node_converted = FALSE;
    gchar           *fullpath = NULL;

    if ((NULL == setting) || (NULL == tnode) 
            || (TREENODE_IS_TRAVERSED (tnode))){
        return FALSE;
    }

    TREENODE_SET_TRAVERSED (tnode, 1);  /* avoid infinite loop for symlink */

    fsexam_errno = ERR_OK;

    /* get stored error during tree construction */
    if (TREENODE_FLAG_REMOTE_FILE (tnode)) {
        fsexam_errno = ERR_IGNORE_REMOTE_FILE;
    }else if (TREENODE_FLAG_HIDDEN_FILE (tnode)) {
        fsexam_errno = ERR_IGNORE_HIDDEN_FILE;
    }else if (TREENODE_FLAG_UTF8_ALREADY (tnode)) {
        fsexam_errno = ERR_NAME_UTF8_ALREADY;
    }

    if (fsexam_errno != ERR_OK) {
        fullpath = fsexam_tree_get_path (node, FALSE);
        fsexam_log_puts (setting->log_info, fullpath, NULL);
        if (setting->display_msg) {
            setting->display_msg (fullpath, fsexam_error_get_msg ());
        }

        if (fsexam_errno != ERR_NAME_UTF8_ALREADY)
            goto done;
    }

    if (TREENODE_IS_NEED_CONVERT (tnode)) {
        gchar    *dir = fsexam_tree_get_path (node->parent, FALSE);
        gchar    *bname = tnode->orig;
        gchar    *newname = NULL;

        node_converted = real_convert (setting, dir, bname, &newname);

        if (node_converted && newname != NULL) {
            TREENODE_SET_CONVERTED (tnode, 1);
            TREENODE_SET_UTF8 (tnode, newname);
        }
        g_free (fullpath);
        fullpath = g_strdup_printf ("%s/%s", dir, newname ? newname : bname);

        g_free (dir);

        if (setting->flags & FSEXAM_SETTING_FLAGS_STOP)
            goto done;
    }

    if (TREENODE_IS_SPECIAL (tnode)) {
        if (fullpath == NULL)
                fullpath = fsexam_tree_get_path (node, FALSE);

        /* 
         * archive or compress filename may be converted already,
         * so get the original name.
         */
        gchar *hist_search_path = fsexam_tree_get_path (node, TRUE);

        fsexam_compress_convert_name (setting, 
                                      fullpath, 
                                      hist_search_path, 
                                      FALSE);

        g_free (hist_search_path);
    }else if (TREENODE_IS_SYMLINK (tnode)) {
        if (TREENODE_FLAG_TARGET_NOTEXIST (tnode)) {
            if (fullpath == NULL)
                fullpath = fsexam_tree_get_path (node, FALSE);
            
            fsexam_errno = ERR_SYMLINK_TARGET_NOEXIST;
            fsexam_log_puts (setting->log_info, fullpath, NULL);
            if (setting->display_msg) {
                setting->display_msg (fullpath, fsexam_error_get_msg ());
            }
        }else if (!setting->pref->no_check_symlink_content) {
            /* follow has been handled during tree construction */
            GNode   *gnode = TREENODE_GET_TARGET (tnode);
            
            if ((gnode != NULL) && (gnode->data != NULL)) 
                if (convert_symlink_target_node (gnode, setting, FALSE)) 
                    relink_symlink (node, gnode, setting);
        }
    } else if ((setting->pref->recursive && TREENODE_IS_RECURSIVE (tnode))
            && (!setting->pref->follow) 
            && setting->pref->no_check_symlink_content){
        convert_directory (setting, node);
    }

done:
    g_free (fullpath);

    return node_converted;
}

/*====================================================================
 *  Function Name:  dryrun_convert_node
 *
 *  Parameters:
 *      GNode *node:    one node created from dryrun result file.
 *      gpointer data:  is FSEXAM_setting passed from g_node_traverse.
 *
 *  Desc:
 *      Convert one node come from dryrun result file.
 *
 *      We need handle various error here.
 *
 *  Return value:
 *      Always return TRUE, otherwise g_node_traverse will stop.
 *
 *  Exception:
 *
 *  Author:     Yandong Yao 2006/09/05
 ========================================================================*/ 
static gboolean
dryrun_convert_node (GNode *node, gpointer data)
{
    FSEXAM_setting  *setting = (FSEXAM_setting *)data;
    TreeNode        *tnode = node->data;
    gchar            *path = NULL;
    gboolean        ret;

    if ((NULL == setting)
            || (NULL == tnode)
            || TREENODE_IS_TRAVERSED (tnode) 
            || (TREENODE_GET_UTF8 (tnode) == NULL) 
            || (! TREENODE_IS_NEED_CONVERT (tnode))) {   /* Ingore symlink */
        return FALSE;
    }

    TREENODE_SET_TRAVERSED (tnode, 1);

    path = fsexam_tree_get_path (node->parent, FALSE);  /* get real path */

    ret = write_to_disk (setting, 
                        path, 
                        TREENODE_GET_ORIG (tnode),
                        TREENODE_GET_UTF8 (tnode),
                        TREENODE_GET_ID (tnode),        /* from encoding    */
                        encoding2id ("UTF-8"),          /* to encoding      */
                        NULL);

    /* update TreeNode */
    if (ret) {
        TREENODE_SET_CONVERTED (tnode, 1);  
    }

    g_free (path);

    return FALSE;
}

/*====================================================================
 *  Function Name:  restore_convert_node
 *
 *  Parameters:
 *      GNode *node:   node need restore
 *      gpointer data: is FSEXAM_setting passed from g_node_traverse.
 *
 *  Desc:
 *      restore one node come from history file
 *      We need handle various error here.
 *
 *  Return value:
 *      Always return FALSE, otherwise g_node_traverse will stop.
 *
 *  Author:     Yandong Yao 2006/11/27
 ========================================================================*/ 
static gboolean
restore_convert_node (GNode *node, gpointer data)
{
    wrapped_restore_convert_node (node, data);

    return FALSE;
}

/* 
 * Return TRUE if node has been converted.
 */
static gboolean
wrapped_restore_convert_node (GNode *node, gpointer data)
{
    FSEXAM_setting  *setting = (FSEXAM_setting *)data;  
    TreeNode        *tnode = (node->data);
    gboolean        node_converted = FALSE;
    gchar           *dirpath = NULL;
    gchar           *fullpath = NULL;

    if ((NULL == setting) || (NULL == tnode) 
            || TREENODE_IS_TRAVERSED (tnode)) { 
        return FALSE;
    }

    TREENODE_SET_TRAVERSED (tnode, 1);

    dirpath = fsexam_tree_get_path (node->parent, FALSE);
    fullpath = fsexam_tree_get_path (node, FALSE);

    if (dirpath == NULL) {
        g_free (fullpath);
        return FALSE;
    }

    fsexam_errno = ERR_OK;

    /* do we need convert it? */
    if (TREENODE_FLAG_REMOTE_FILE (tnode)) {
        fsexam_errno = ERR_IGNORE_REMOTE_FILE;
    }else if (TREENODE_FLAG_HIDDEN_FILE (tnode)) {
        fsexam_errno = ERR_IGNORE_HIDDEN_FILE;
    }

    if (fsexam_errno != ERR_OK) {
        fsexam_log_puts (setting->log_info, fullpath, NULL);
        if (setting->display_msg) {
            setting->display_msg (fullpath, fsexam_error_get_msg ());
        }

        goto done;
    }

    if (TREENODE_GET_ORIG (tnode) != NULL && TREENODE_GET_UTF8 (tnode) != NULL){
        if (TREENODE_IS_REVERSE (tnode)) {
            node_converted = write_to_disk (setting,
                                            dirpath,
                                            TREENODE_GET_ORIG (tnode),
                                            TREENODE_GET_UTF8 (tnode),
                                            encoding2id ("UTF-8"),
                                            TREENODE_GET_ID (tnode),
                                            NULL);

        }else{
            node_converted = write_to_disk (setting,
                                            dirpath,
                                            TREENODE_GET_ORIG (tnode),
                                            TREENODE_GET_UTF8 (tnode),
                                            TREENODE_GET_ID (tnode),
                                            encoding2id ("UTF-8"),
                                            NULL);
        }

        if (node_converted)
            TREENODE_SET_CONVERTED (tnode, 1);

        g_free (fullpath);
        fullpath = fsexam_tree_get_path (node, FALSE);
    }

    if (TREENODE_IS_SPECIAL (tnode)) {
        /* hist_search_path use the original name of special file */
        gchar *hist_search_path = fsexam_tree_get_path (node, TRUE);

        /* Restore special type file name */
        fsexam_compress_convert_name (setting, 
                                      fullpath, 
                                      hist_search_path, 
                                      TRUE);

        g_free (hist_search_path);
    }else if (TREENODE_IS_SYMLINK (tnode)) {
        if (TREENODE_FLAG_TARGET_NOTEXIST (tnode)) {
            fsexam_errno = ERR_SYMLINK_TARGET_NOEXIST;
            fsexam_log_puts (setting->log_info, fullpath, NULL);
            if (setting->display_msg) {
                setting->display_msg (fullpath, fsexam_error_get_msg ());
            }
        }else if (!setting->pref->no_check_symlink_content) {
            GNode   *gnode = TREENODE_GET_TARGET (tnode);

            if ((gnode != NULL) && (gnode->data != NULL)) 
                if (convert_symlink_target_node (gnode, setting, TRUE)) 
                    relink_symlink (node, gnode, setting);
        }
    }

done:
    g_free (fullpath);
    g_free (dirpath);

    return node_converted;
}

/*
 *  Display candidate list for CLI.
 *  Return the total number of candidates.
 */
static gint
construct_candidate_list (GList *list, gboolean forname)
{
    gint num_candidate = 0;

    if (list == NULL)
        return -1;

    fprintf (stdout, _("Candidate list:\n"));
    fprintf (stdout, _("\t[No.] Encoding name\tConversion Result\n"));

    while (list != NULL) {
        Encoding *encode = (Encoding *)list->data;

        list = g_list_next (list);

        if (encode->score == FAIL) 
            continue;

        if (forname)
            fprintf (stdout, "\t[%i]   %s\t\t%s\n", 
                     num_candidate, 
                     id2encoding (encode->encodingID),
                     encode->u.converted_text);
        else {                          /* content */
            fprintf (stdout, "\t[%i]   %s\t\t%s\n", 
                     num_candidate, 
                     id2encoding (encode->encodingID),
                     encode->u.contents);
        }

        num_candidate++;
    }

    return num_candidate;
}

static gboolean
fsexam_convert_scenario_for_name (FSEXAM_setting *setting)
{
    g_return_val_if_fail (setting != NULL, FALSE);

    GSList   *slist = NULL;
    GNode    *root = NULL;
    gboolean ret;

    ret = fsexam_dryrun_process (setting->dryrun_info, &slist);

    if ((! ret) || (NULL == slist)) {
        fsexam_log_puts (setting->log_info, NULL, NULL);
        if (setting->display_msg)
            setting->display_msg (NULL, fsexam_error_get_msg ());

        setting->display_stats (setting);   /* display statastics */
        fsexam_log_flush (setting->log_info);
        return FALSE;
    }

    root = fsexam_tree_construct_from_dryrun (slist, setting);
    fsexam_dryrun_item_slist_free (slist);

    if (root == NULL) {
        fsexam_errno = ERR_TREE_IS_EMPTY;
        fsexam_log_puts (setting->log_info, NULL, NULL);
        if (setting->display_msg)
            setting->display_msg (NULL, fsexam_error_get_msg ());

        return FALSE;
    }

    if (fsexam_debug () 
            & (FSEXAM_DBG_OPTION | FSEXAM_DBG_TREE | FSEXAM_DBG_FILELIST)) {
        return TRUE;
    }

    setting->flags |= FSEXAM_SETTING_FLAGS_DIFF_SERIAL;

    g_node_traverse (root, 
                    G_PRE_ORDER, 
                    G_TRAVERSE_ALL, 
                    -1, 
                    dryrun_convert_node, 
                    setting);

    setting->display_stats (setting);   /* display statastics */
    fsexam_log_flush (setting->log_info);
    setting->flags = 0;
    fsexam_tree_destroy (root);

    return TRUE;

}

static gboolean
fsexam_restore_name (FSEXAM_setting *setting, GList *list)
{
    g_return_val_if_fail (setting != NULL, FALSE);

    GNode *root = NULL;

    root = fsexam_tree_construct_from_history (list, 
                                               setting->hist_info, 
                                               setting);

    if ((list == NULL) || (root == NULL)){
        fsexam_errno = ERR_TREE_IS_EMPTY;
        fsexam_log_puts (setting->log_info, NULL, NULL);
        if (setting->display_msg)
            setting->display_msg (NULL, fsexam_error_get_msg ());

        return FALSE;
    }

    setting->flags |= FSEXAM_SETTING_FLAGS_DIFF_SERIAL;

    if (fsexam_debug () 
            & (FSEXAM_DBG_OPTION | FSEXAM_DBG_TREE | FSEXAM_DBG_FILELIST)) {
        return TRUE;
    }

    g_node_traverse (root, 
                    G_PRE_ORDER, 
                    G_TRAVERSE_ALL, 
                    -1, 
                    restore_convert_node, 
                    setting);

    setting->display_stats (setting);   /* display statastics */
    fsexam_log_flush (setting->log_info);
    setting->flags = 0;
    fsexam_tree_destroy (root);

    return TRUE;
}

/* --------------   Public API  ------------------------ */

/*
 *  find one non exist name through append suffix.
 *  need free by caller
 */
gchar *
find_non_exist_name (const gchar *name)
{
    gchar   tmp[PATH_MAX];
    gint    i;

    for (i = 0; ; i++) {
        if (i) {
            g_snprintf (tmp, PATH_MAX - 1, "%s.%s.%d", name, SUFFIX, i);
        }else{
            g_snprintf (tmp, PATH_MAX - 1, "%s.%s", name, SUFFIX);
        }

        if (! g_file_test (tmp, G_FILE_TEST_EXISTS))
            return g_strdup (tmp);
    }
}

/*
 * Convert single file's fullpath, don't care any flags
 */
gboolean
fsexam_convert_single_filename (FSEXAM_setting *setting, 
                                const gchar *filename,
                                gchar **result_name)
{
    GNode    *root = NULL;
    GNode    *tmp = NULL;
    gboolean node_converted = FALSE;

    if ((filename == NULL) || (setting == NULL)) {
        return FALSE;
    }

    fsexam_setting_reset_stats (setting);

    if ((root = fsexam_tree_construct_from_single_file (filename)) == NULL) {
        fsexam_errno = ERR_TREE_IS_EMPTY;
        fsexam_log_puts (setting->log_info, NULL, NULL);
        if (setting->display_msg)
            setting->display_msg (NULL, fsexam_error_get_msg ());

        return FALSE;
    }

    setting->flags |= FSEXAM_SETTING_FLAGS_DIFF_SERIAL;

    tmp = root;
    while ((tmp = g_node_first_child (tmp)) != NULL) {
        TreeNode *tnode = tmp->data;
        gchar    *bname = tnode->orig;
        gchar    *dir = NULL;
        gchar    *newname = NULL;

        if (! TREENODE_IS_NEED_CONVERT (tnode)) {
            continue;
        }

        dir = fsexam_tree_get_path (tmp->parent, FALSE);
        node_converted = real_convert (setting, dir, bname, &newname);
        g_free (dir);

        if (node_converted) {
            TREENODE_SET_CONVERTED (tnode, 1);
            TREENODE_SET_UTF8 (tnode, newname);

            if ((G_NODE_IS_LEAF (tmp)) && (result_name != NULL)) {
                /* last node */ 
                *result_name = fsexam_tree_get_path (tmp, FALSE);
            }
        }else{
            g_free (newname);
            break;      /* fsexam_errno store the reason */
        }
    }
    
    setting->flags = 0;
    fsexam_tree_destroy (root);

    return node_converted;
}

gboolean
fsexam_convert_filename (FSEXAM_setting *setting, const gchar *filename)
{
    g_return_val_if_fail (setting != NULL, FALSE);

    GList       *list = NULL;
    gboolean    ret;

    if (filename != NULL) 
        list = g_list_prepend (list, (gpointer) filename);

    ret = fsexam_convert_filename_batch (setting, list);

    fsexam_list_free (list);

    return ret;
}

gboolean
fsexam_convert_filename_batch (FSEXAM_setting *setting, GList *list)
{
    g_return_val_if_fail (setting != NULL, FALSE);

    GNode *root = NULL;

    fsexam_setting_reset_stats (setting);

    if (list != NULL) 
        root = fsexam_tree_construct_from_list (list, setting);

    if ((NULL == list) || (NULL == root)){
        fsexam_errno = ERR_TREE_IS_EMPTY;
        fsexam_log_puts (setting->log_info, NULL, NULL);
        if (setting->display_msg)
            setting->display_msg (NULL, fsexam_error_get_msg ());

        return FALSE;
    }

    if (fsexam_debug () 
            & (FSEXAM_DBG_OPTION | FSEXAM_DBG_TREE | FSEXAM_DBG_FILELIST)) {
        return TRUE;
    }

    setting->flags |= FSEXAM_SETTING_FLAGS_DIFF_SERIAL;

    g_node_traverse (root, 
                     G_PRE_ORDER, 
                     G_TRAVERSE_ALL, 
                     -1, 
                     convert_node,  /* Return TRUE will stop the traverse */
                     setting);

    setting->display_stats (setting);   /* display statastics */
    fsexam_log_flush (setting->log_info);
    setting->flags = 0;             /* clear all flags after conversion */
    fsexam_tree_destroy (root);

    return TRUE;
}


//TODO: Add format validation
gboolean
fsexam_convert_scenario (FSEXAM_setting *setting)
{
    g_return_val_if_fail (setting != NULL, FALSE);

    ConvType    type;
    gboolean    ret;

    fsexam_setting_reset_stats (setting);

    if (setting->dryrun_info == NULL) {
        fprintf (stderr, _("Can't access dry run result information.\n"));
        return FALSE;
    }

    if (!fsexam_dryrun_get_convtype (setting->dryrun_info, &type))
        return FALSE;

    if (type == ConvName) {
        ret = fsexam_convert_scenario_for_name (setting);
    }else{
        ret = fsexam_convert_scenario_for_content (setting);
    }

    return ret;
}

gboolean 
fsexam_restore (FSEXAM_setting *setting, GList *list, ConvType restore_type)
{
    gboolean ret;

    fsexam_setting_reset_stats (setting);

    if (restore_type == RestoreConvName) {
        ret = fsexam_restore_name (setting, list);
    }else if (restore_type == RestoreConvContent) {
        ret = fsexam_restore_content (setting, list);
    }else{
        g_print (_("Don't support this restore type.\n"));
        ret = FALSE;
    }

    return ret;
}


/* ---------------  call back function for CLI ------------  */

#define INPUT_LEN 10

/*
 *  Get the index of user's selection.
 *  -1 returned if no selection.
 */
gint 
get_index_default (GList *encoding_list, gboolean forname)
{
    gint     num_candidate;
    gchar    input[INPUT_LEN];
    gint     response;
    gint     result_index;

    num_candidate = construct_candidate_list (encoding_list, forname);

    if (num_candidate < 1) {
        fprintf (stderr, _("No proper encoding.\n"));
        return -1;
    }

    do {
        fprintf (stdout, _("Please select No.('s' to skip): "));
        memset (input, 0, sizeof (input));  
        fflush (stdin);             /* discard excess data */
        fgets (input, sizeof(input), stdin);

        if ((*input == '\n') || (*input == '\0'))   /* Return or Empty */
            continue;

        input[strlen (input) - 1] = '\0';   /* remove trailing '\n' */

        if (g_ascii_strcasecmp (input, "s") == 0) {
            response = -1;
            break;
        }else{
            gchar *tmp = input;

            for (; ((*tmp >= '0') && (*tmp <= '9') && (*tmp != '\0')); tmp++)
                ;

            if (*tmp != '\0')
                continue;

            response = atoi (input);    /* input belong [0-9]{1, 9}  */

            if ((response < 0) || (response > num_candidate - 1))
                continue;
            else
                break;
        }
    } while (TRUE);

    if (response == -1)
        return -1;

    fsexam_encoding_iterate_with_func (encoding_list,
                fsexam_encoding_translate_index,
                &response,
                &result_index);

    return result_index;
}

/*
 *  display_msg func for CLI
 */
void
display_msg_default (const gchar *filename, const gchar *msg)
{
    if ((filename == NULL) || (msg == NULL))
        return;

    if (filename) {
        if (g_utf8_validate (filename, -1, NULL)) {
            fprintf (stderr, "%s: %s\n", filename, msg ? msg : "");
        }else{
            gchar *uri = g_filename_to_uri (filename, NULL, NULL);

            if (uri == NULL) {  /* such as filename doesn't exist */
                uri = fsexam_string_escape (filename);
            }

            fprintf (stderr, "%s: %s\n", uri, msg ? msg : "");
            g_free (uri);
        }
    }else{ 
        fprintf (stderr, "%s\n", msg);
    }

    return;
}