Esempio n. 1
0
void fm_path_init()
{
	const char* sep, *name;
	FmPath* tmp, *parent;

	/* path object of root dir */
	root = fm_path_new_child(NULL, "/");
	home_dir = g_get_home_dir();
	home_len = strlen(home_dir);

	/* build path object for home dir */
	name = home_dir + 1; /* skip leading / */
	parent = root;
	while( sep = strchr(name, '/') )
	{
		int len = (sep - name);
		/* ref counting is not a problem here since this path component
		 * will exist till the termination of the program. So mem leak is ok. */
		tmp = fm_path_new_child_len(parent, name, len);
		name = sep + 1;
		parent = tmp;
	}
	home = fm_path_new_child(parent, name);

	desktop_dir = g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP);
	desktop_len = strlen(desktop_dir);

	/* build path object for desktop dir */
	name = desktop_dir + home_len + 1; /* skip home dir part / */
	parent = home;
	while( sep = strchr(name, '/') )
	{
		int len = (sep - name);
		/* ref counting is not a problem here since this path component
		 * will exist till the termination of the program. So mem leak is ok. */
		tmp = fm_path_new_child_len(parent, name, len);
		name = sep + 1;
		parent = tmp;
	}
	desktop = fm_path_new_child(parent, name);

	/* build path object for trash can */
    /* FIXME: currently there are problems with URIs. using trash:/ here will cause problems. */
	trash_root = fm_path_new_child(NULL, "trash:///");
	trash_root->flags |= (FM_PATH_IS_TRASH|FM_PATH_IS_VIRTUAL|FM_PATH_IS_LOCAL);

    apps_root = fm_path_new_child(NULL, "applications:///");
	apps_root->flags |= (FM_PATH_IS_VIRTUAL);
}
Esempio n. 2
0
/**
 * fm_folder_config_open
 * @path: path to get config
 *
 * Searches for settings in the cache that are specific to @path. Locks
 * the cache. Returned descriptor can be used for access to settings.
 *
 * Returns: (transfer full): new configuration descriptor.
 *
 * Since: 1.2.0
 */
FmFolderConfig *fm_folder_config_open(FmPath *path)
{
    FmFolderConfig *fc = g_slice_new(FmFolderConfig);
    FmPath *sub_path;

    fc->changed = FALSE;
    /* clear .directory file first */
    sub_path = fm_path_new_child(path, ".directory");
    fc->filepath = fm_path_to_str(sub_path);
    fm_path_unref(sub_path);
    if (g_file_test(fc->filepath, G_FILE_TEST_EXISTS))
    {
        fc->kf = g_key_file_new();
        if (g_key_file_load_from_file(fc->kf, fc->filepath,
                                      G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
                                      NULL) &&
            g_key_file_has_group(fc->kf, "File Manager"))
        {
            fc->group = "File Manager";
            return fc;
        }
        g_key_file_free(fc->kf);
    }
    g_free(fc->filepath);
    fc->filepath = NULL;
    fc->group = fm_path_to_str(path);
    G_LOCK(cache);
    fc->kf = fc_cache;
    return fc;
}
Esempio n. 3
0
/**
 * fm_path_new_relative
 * @parent: a parent path
 * @rel: a path relative to @parent in glib filename encoding.  (can be
 * non-UTF-8). However this should not be a escaped ASCII string used in
 * URI. If you're building a relative path for a URI, and the relative
 * path is escaped, you have to unescape it first.
 *
 * For example, if @parent is "http://wiki.lxde.org/" and @rel is
 * "zh/%E9%A6%96%E9%A0%81", you have to unescape the relative path
 * prior to passing it to fm_path_new_relative ().
 *
 * If @parent is NULL, this works the same as fm_path_new_for_str (@rel)
 *
 * Returns: a newly created FmPath for the path. You have to call
 * fm_path_unref () when it's no longer needed.
 */
FmPath *fm_path_new_relative (FmPath *parent, const char *rel)
{
    FmPath *path;
    
    if (G_UNLIKELY (!rel || !*rel)) // relative path is empty
        return parent ? fm_path_ref (parent) : fm_path_ref (root_path); // return parent

    if (G_LIKELY (parent))
    {
        char *sep;
        
        // remove leading slashes
        while (*rel == '/')
            ++rel;
        
        if (!*rel)
            path = fm_path_ref (parent);
        else
        {
#if 0       // FIXME_pcm: Let's optimize this later. Make things working first is more important.
            // use some pre-defined paths when possible
            if (G_UNLIKELY (parent == root_path))
            {
                if (strcmp (home_dir + 1, rel) == 0)
                    return fm_path_ref (home_path);
                if (strcmp (desktop_dir + 1, rel) == 0)
                    return fm_path_ref (desktop_dir);
            }
#endif

            sep = strchr (rel, '/');
            if (sep)
            {
                FmPath *new_parent = fm_path_new_child_len (parent, rel, sep - rel);
                path = fm_path_new_relative (new_parent, sep + 1);
                fm_path_unref (new_parent);
            }
            else
            {
                path = fm_path_new_child (parent, rel);
            }
        }
    }
    else // this is actaully a full path
        path = fm_path_new_for_str (rel);
    
    return path;
}
Esempio n. 4
0
static void test_path_child()
{
    FmPath* parent = fm_path_get_home();
    FmPath* path;
    g_print("\n");
    path = fm_path_new_child(parent, "child");
    g_printf("path->name = %s\n", path->name);
    g_assert_cmpstr(path->name, ==, "child");
    fm_path_unref(path);

    path = fm_path_new_child(parent, "child/");
    g_printf("path->name = %s\n", path->name);
    g_assert_cmpstr(path->name, ==, "child");
    fm_path_unref(path);

    path = fm_path_new_child(parent, "child///");
    g_printf("path->name = %s\n", path->name);
    g_assert_cmpstr(path->name, ==, "child");
    fm_path_unref(path);

    path = fm_path_new_child(parent, "..");
    g_printf("path->name = %s\n", path->name);
    g_assert(path == parent->parent);
    fm_path_unref(path);

    path = fm_path_new_child(parent, "../");
    g_printf("path->name = %s\n", path->name);
    g_assert(path == parent->parent);
    fm_path_unref(path);

    path = fm_path_new_child(parent, "/");
    g_assert(path == parent);
    fm_path_unref(path);

    parent = fm_path_get_root();

    path = fm_path_new_child(parent, "..");
    g_assert(path == parent);
    fm_path_unref(path);

    path = fm_path_new_child(parent, "../");
    g_assert(path == parent);
    fm_path_unref(path);

    parent = NULL;
    path = fm_path_new_child(parent, "/");
    g_printf("path->name = %s\n", path->name);
    g_assert_cmpstr(path->name, ==, "/");
    fm_path_unref(path);

    path = fm_path_new_child(parent, "//");
    g_printf("path->name = %s\n", path->name);
    g_assert_cmpstr(path->name, ==, "/");
    fm_path_unref(path);

    path = fm_path_new_child(parent, "///");
    g_printf("path->name = %s\n", path->name);
    g_assert_cmpstr(path->name, ==, "/");
    fm_path_unref(path);

/*  FIXME: how to handle this case?
    path = fm_path_new_child(parent, "/test");
    g_printf("path->name = %s\n", path->name);
    g_assert_cmpstr(path->name, ==, "/test/");
    fm_path_unref(path);
*/

    path = fm_path_new_child(parent, "trash:");
    g_printf("path->name = %s\n", path->name);
    g_assert_cmpstr(path->name, ==, "trash:///");
    fm_path_unref(path);

    path = fm_path_new_child(parent, "trash:/");
    g_printf("path->name = %s\n", path->name);
    g_assert_cmpstr(path->name, ==, "trash:///");
    fm_path_unref(path);

    path = fm_path_new_child(parent, "trash:////");
    g_printf("path->name = %s\n", path->name);
    g_assert_cmpstr(path->name, ==, "trash:///");
    fm_path_unref(path);

    path = fm_path_new_child(parent, "..");
    g_printf("path->name = %s\n", path->name);
    g_assert_cmpstr(path->name, ==, "/");
    fm_path_unref(path);

    path = fm_path_new_child(parent, "../");
    g_assert_cmpstr(path->name, ==, "/");
    fm_path_unref(path);

/*
    path = fm_path_new_child(parent, "..");
    g_assert(path == NULL);

    path = fm_path_new_child(parent, ".");
    g_assert(path == NULL);
*/
}
static gboolean fm_dir_list_job_run_gio(FmDirListJob* job)
{
    GFileEnumerator *enu;
    GFileInfo *inf;
    FmFileInfo* fi;
    GError *err = NULL;
    FmJob* fmjob = FM_JOB(job);
    GFile* gf;
    const char* query;

    gf = fm_path_to_gfile(job->dir_path);
_retry:
    inf = g_file_query_info(gf, gfile_info_query_attribs, 0, fm_job_get_cancellable(fmjob), &err);
    if(!inf )
    {
        FmJobErrorAction act = fm_job_emit_error(fmjob, err, FM_JOB_ERROR_MODERATE);
        g_error_free(err);
        if( act == FM_JOB_RETRY )
        {
            err = NULL;
            goto _retry;
        }
        else
        {
            g_object_unref(gf);
            return FALSE;
        }
    }

    if( g_file_info_get_file_type(inf) != G_FILE_TYPE_DIRECTORY)
    {
        char *path_str = fm_path_to_str(job->dir_path);
        err = g_error_new(G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY,
                          _("The specified directory '%s' is not valid"),
                          path_str);
        fm_job_emit_error(fmjob, err, FM_JOB_ERROR_CRITICAL);
        g_free(path_str);
        g_error_free(err);
        g_object_unref(gf);
        g_object_unref(inf);
        return FALSE;
    }

    /* check if FS is R/O and set attr. into inf */
    _fm_file_info_job_update_fs_readonly(gf, inf, NULL, NULL);

    job->dir_fi = fm_file_info_new_from_g_file_data(gf, inf, job->dir_path);
    g_object_unref(inf);

    if(G_UNLIKELY(job->flags & FM_DIR_LIST_JOB_DIR_ONLY))
    {
        query = G_FILE_ATTRIBUTE_STANDARD_TYPE","G_FILE_ATTRIBUTE_STANDARD_NAME","
                G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN","G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP","
                G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK","G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL","
                G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME","
                G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME","G_FILE_ATTRIBUTE_STANDARD_ICON","
                G_FILE_ATTRIBUTE_STANDARD_SIZE","G_FILE_ATTRIBUTE_STANDARD_TARGET_URI","
                "unix::*,time::*,access::*,id::filesystem";
    }
    else
        query = gfile_info_query_attribs;

    enu = g_file_enumerate_children (gf, query, 0, fm_job_get_cancellable(fmjob), &err);
    g_object_unref(gf);
    if(enu)
    {
        while( ! fm_job_is_cancelled(fmjob) )
        {
            inf = g_file_enumerator_next_file(enu, fm_job_get_cancellable(fmjob), &err);
            if(inf)
            {
                FmPath *dir, *sub;
                GFile *child;
                if(G_UNLIKELY(job->flags & FM_DIR_LIST_JOB_DIR_ONLY))
                {
                    /* FIXME: handle symlinks */
                    if(g_file_info_get_file_type(inf) != G_FILE_TYPE_DIRECTORY)
                    {
                        g_object_unref(inf);
                        continue;
                    }
                }

                /* virtual folders may return children not within them */
                dir = fm_path_new_for_gfile(g_file_enumerator_get_container(enu));
                if (fm_path_equal(job->dir_path, dir))
                    sub = fm_path_new_child(job->dir_path, g_file_info_get_name(inf));
                else
                    sub = fm_path_new_child(dir, g_file_info_get_name(inf));
                child = g_file_get_child(g_file_enumerator_get_container(enu),
                                         g_file_info_get_name(inf));
                if (g_file_info_get_file_type(inf) == G_FILE_TYPE_DIRECTORY)
                    /* for dir: check if its FS is R/O and set attr. into inf */
                    _fm_file_info_job_update_fs_readonly(child, inf, NULL, NULL);
                fi = fm_file_info_new_from_g_file_data(child, inf, sub);
                fm_path_unref(sub);
                fm_path_unref(dir);
                g_object_unref(child);
                fm_dir_list_job_add_found_file(job, fi);
                fm_file_info_unref(fi);
            }
            else
            {
                if(err)
                {
                    FmJobErrorAction act = fm_job_emit_error(fmjob, err, FM_JOB_ERROR_MILD);
                    g_error_free(err);
                    /* FM_JOB_RETRY is not supported. */
                    if(act == FM_JOB_ABORT)
                        fm_job_cancel(fmjob);
                }
                /* otherwise it's EOL */
                break;
            }
            g_object_unref(inf);
        }
        g_file_enumerator_close(enu, NULL, &err);
        g_object_unref(enu);
    }
    else
    {
        fm_job_emit_error(fmjob, err, FM_JOB_ERROR_CRITICAL);
        g_error_free(err);
        return FALSE;
    }
    return TRUE;
}
static gboolean fm_dir_list_job_run_posix(FmDirListJob* job)
{
    FmJob* fmjob = FM_JOB(job);
    FmFileInfo* fi;
    GError *err = NULL;
    char* path_str;
    GDir* dir;

    path_str = fm_path_to_str(job->dir_path);

    fi = _new_info_for_native_file(job, job->dir_path, path_str, NULL);
    if(fi)
    {
        if(! fm_file_info_is_dir(fi))
        {
            err = g_error_new(G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY,
                              _("The specified directory '%s' is not valid"),
                              path_str);
            fm_file_info_unref(fi);
            fm_job_emit_error(fmjob, err, FM_JOB_ERROR_CRITICAL);
            g_error_free(err);
            g_free(path_str);
            return FALSE;
        }
        job->dir_fi = fi;
    }
    else
    {
        err = g_error_new(G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY,
                          _("The specified directory '%s' is not valid"),
                          path_str);
        fm_job_emit_error(fmjob, err, FM_JOB_ERROR_CRITICAL);
        g_error_free(err);
        g_free(path_str);
        return FALSE;
    }

    dir = g_dir_open(path_str, 0, &err);
    if( dir )
    {
        const char* name;
        GString* fpath = g_string_sized_new(4096);
        int dir_len = strlen(path_str);
        g_string_append_len(fpath, path_str, dir_len);
        if(fpath->str[dir_len-1] != '/')
        {
            g_string_append_c(fpath, '/');
            ++dir_len;
        }
        while( ! fm_job_is_cancelled(fmjob) && (name = g_dir_read_name(dir)) )
        {
            FmPath* new_path;
            g_string_truncate(fpath, dir_len);
            g_string_append(fpath, name);

            if(job->flags & FM_DIR_LIST_JOB_DIR_ONLY) /* if we only want directories */
            {
                struct stat st;
                /* FIXME: this results in an additional stat() call, which is inefficient */
                if(stat(fpath->str, &st) == -1 || !S_ISDIR(st.st_mode))
                    continue;
            }

            new_path = fm_path_new_child(job->dir_path, name);

        _retry:
            fi = _new_info_for_native_file(job, new_path, fpath->str, &err);
            if (fi == NULL) /* we got a damaged file */
            {
                FmJobErrorAction act = fm_job_emit_error(fmjob, err, FM_JOB_ERROR_MILD);
                GFile *gf;
                GFileInfo *inf;
                gchar *disp_basename;

                g_error_free(err);
                err = NULL;
                if(act == FM_JOB_RETRY)
                    goto _retry;
                /* bug #3615271: Damaged mountpoint isn't shown
                   let make a simple file info then */
                inf = g_file_info_new();
                gf = fm_path_to_gfile(new_path);
                g_file_info_set_file_type(inf, G_FILE_TYPE_UNKNOWN);
                g_file_info_set_name(inf, name);
                disp_basename = g_filename_display_basename(fpath->str);
                g_file_info_set_display_name(inf, disp_basename);
                g_free(disp_basename);
                g_file_info_set_content_type(inf, "inode/x-corrupted");
                fi = fm_file_info_new_from_g_file_data(gf, inf, new_path);
                g_object_unref(inf);
                g_object_unref(gf);
            }
            fm_dir_list_job_add_found_file(job, fi);
            fm_file_info_unref(fi);
            fm_path_unref(new_path);
        }
        g_string_free(fpath, TRUE);
        g_dir_close(dir);
    }
    else
    {
        fm_job_emit_error(fmjob, err, FM_JOB_ERROR_CRITICAL);
        g_error_free(err);
    }
    g_free(path_str);
    return TRUE;
}
Esempio n. 7
0
gboolean on_idle(FmFolder* folder)
{
    GSList* l;
    FmFileInfoJob* job = NULL;
    FmPath* path;
    folder->idle_handler = 0;
    if(folder->files_to_update || folder->files_to_add)
        job = (FmFileInfoJob*)fm_file_info_job_new(NULL, 0);

    if(folder->files_to_update)
    {
        GSList* prev = NULL;
        for(l=folder->files_to_update;l;)
        {
            /* if a file is already in files_to_add, remove it. */
            if(g_slist_find_custom(folder->files_to_add, l->data, (GCompareFunc)strcmp))
            {
                GSList* tmp = l;
                l=l->next;
                if(G_LIKELY(prev))
                    prev->next = l;
                g_free(tmp->data);
                g_slist_free_1(tmp);
                if(G_UNLIKELY(tmp == folder->files_to_update))
                    folder->files_to_update = l;
                continue;
            }
            path = fm_path_new_child(folder->dir_path, (char*)l->data);
            fm_file_info_job_add(job, path);
            fm_path_unref(path);
            g_free(l->data);

            prev = l;
            l=l->next;
        }
        g_slist_free(folder->files_to_update);
        folder->files_to_update = NULL;
    }

    if(folder->files_to_add)
    {
        for(l=folder->files_to_add;l;l=l->next)
        {
            path = fm_path_new_child(folder->dir_path, (char*)l->data);
            fm_file_info_job_add(job, path);
            fm_path_unref(path);
            g_free(l->data);
        }
        g_slist_free(folder->files_to_add);
        folder->files_to_add = NULL;
    }

    if(job)
    {
        g_signal_connect(job, "finished", on_file_info_finished, folder);
        folder->pending_jobs = g_slist_prepend(folder->pending_jobs, job);
        fm_job_run_async(FM_JOB(job));
    }

    if(folder->files_to_del)
    {
        GSList* ll;
        for(ll=folder->files_to_del;ll;ll=ll->next)
        {
            GList* l= (GList*)ll->data;
            ll->data = l->data;
            fm_list_delete_link_nounref(folder->files , l);
        }
        g_signal_emit(folder, signals[FILES_REMOVED], 0, folder->files_to_del);
        g_slist_foreach(folder->files_to_del, (GFunc)fm_file_info_unref, NULL);
        g_slist_free(folder->files_to_del);
        folder->files_to_del = NULL;
    }

    return FALSE;
}
Esempio n. 8
0
void on_create_new(GtkAction* action, FmMainWin* win)
{
    FmFolderView* fv = FM_FOLDER_VIEW(win->folder_view);
    const char* name = gtk_action_get_name(action);
    GError* err = NULL;
    FmPath* cwd = fm_folder_view_get_cwd(fv);
    FmPath* dest;
    char* basename;
_retry:
    basename = fm_get_user_input(GTK_WINDOW(win), _("Create New..."), _("Enter a name for the newly created file:"), _("New"));
    if(!basename)
        return;

    dest = fm_path_new_child(cwd, basename);
    g_free(basename);

    if( strcmp(name, "NewFolder") == 0 )
    {
        GFile* gf = fm_path_to_gfile(dest);
        if(!g_file_make_directory(gf, NULL, &err))
        {
            if(err->domain = G_IO_ERROR && err->code == G_IO_ERROR_EXISTS)
            {
                fm_path_unref(dest);
                g_error_free(err);
                g_object_unref(gf);
                err = NULL;
                goto _retry;
            }
            fm_show_error(GTK_WINDOW(win), err->message);
            g_error_free(err);
        }

        if(!err) /* select the newly created file */
        {
            /*FIXME: this doesn't work since the newly created file will
             * only be shown after file-created event was fired on its
             * folder's monitor and after FmFolder handles it in idle
             * handler. So, we cannot select it since it's not yet in
             * the folder model now. */
            /* fm_folder_view_select_file_path(fv, dest); */
        }
        g_object_unref(gf);
    }
    else if( strcmp(name, "NewBlank") == 0 )
    {
        GFile* gf = fm_path_to_gfile(dest);
        GFileOutputStream* f = g_file_create(gf, G_FILE_CREATE_NONE, NULL, &err);
        if(f)
        {
            g_output_stream_close(G_OUTPUT_STREAM(f), NULL, NULL);
            g_object_unref(f);
        }
        else
        {
            if(err->domain = G_IO_ERROR && err->code == G_IO_ERROR_EXISTS)
            {
                fm_path_unref(dest);
                g_error_free(err);
                g_object_unref(gf);
                err = NULL;
                goto _retry;
            }
            fm_show_error(GTK_WINDOW(win), err->message);
            g_error_free(err);
        }

        if(!err) /* select the newly created file */
        {
            /*FIXME: this doesn't work since the newly created file will
             * only be shown after file-created event was fired on its
             * folder's monitor and after FmFolder handles it in idle
             * handler. So, we cannot select it since it's not yet in
             * the folder model now. */
            /* fm_folder_view_select_file_path(fv, dest); */
        }
        g_object_unref(gf);
    }
    else /* templates */
    {

    }
    fm_path_unref(dest);
}
Esempio n. 9
0
gboolean fm_dnd_dest_drag_drop(FmDndDest* dd, GdkDragContext *drag_context,
                               GdkAtom target, guint time)
{
    gboolean ret = FALSE;
    GtkWidget* dest_widget = dd->widget;
    int i;
    for(i = 0; i < G_N_ELEMENTS(fm_default_dnd_dest_targets); ++i)
    {
        if(gdk_atom_intern_static_string(fm_default_dnd_dest_targets[i].target) == target)
        {
            ret = TRUE;
            break;
        }
    }
    if(ret) /* we support this kind of target */
    {
        if(i == FM_DND_DEST_TARGET_XDS) /* if this is XDS */
        {
            guchar *data = NULL;
            gint len = 0;
            GdkAtom text_atom = gdk_atom_intern_static_string("text/plain");
            /* get filename from the source window */
            if(gdk_property_get(drag_context->source_window, xds_target_atom, text_atom,
                                0, 1024, FALSE, NULL, NULL,
                                &len, &data) && data)
            {
                FmFileInfo* dest = fm_dnd_dest_get_dest_file(dd);
                if( dest && fm_file_info_is_dir(dest) )
                {
                    FmPath* path = fm_path_new_child(dest->path, data);
                    char* uri = fm_path_to_uri(path);
                    /* setup the property */
                    gdk_property_change(GDK_DRAWABLE(drag_context->source_window), xds_target_atom,
                                       text_atom, 8, GDK_PROP_MODE_REPLACE, (const guchar *)uri,
                                       strlen(uri) + 1);
                    fm_path_unref(path);
                    g_free(uri);
                }
            }
            else
            {
                fm_show_error(gtk_widget_get_toplevel(dest_widget),
                              _("XDirectSave failed."));
                gdk_property_change(GDK_DRAWABLE(drag_context->source_window), xds_target_atom,
                                   text_atom, 8, GDK_PROP_MODE_REPLACE, (const guchar *)"", 0);
            }
            g_free(data);
            gtk_drag_get_data(dest_widget, drag_context, target, time);
            /* we should call gtk_drag_finish later in data-received callback. */
            return TRUE;
        }

        /* see if the drag files are cached */
        if(dd->src_files)
        {
            /* emit files-dropped signal */
            g_signal_emit(dd, signals[FILES_DROPPED], 0, drag_context->action, dd->info_type, dd->src_files, &ret);
        }
        else /* we don't have the data */
        {
            if(dd->waiting_data) /* if we're still waiting for the data */
            {
                /* FIXME: how to handle this? */
                ret = FALSE;
            }
            else
                ret = FALSE;
        }
        gtk_drag_finish(drag_context, ret, FALSE, time);
    }
    return ret;
}
Esempio n. 10
0
gboolean deep_count_posix(FmDeepCountJob* job, FmPath* fm_path)
{
    FmJob* fmjob = (FmJob*)job;
    char* path = fm_path_to_str(fm_path);
    struct stat st;
    int ret;

_retry_stat:
    if( G_UNLIKELY(job->flags & FM_DC_JOB_FOLLOW_LINKS) )
        ret = stat(path, &st);
    else
        ret = lstat(path, &st);

    if( ret == 0 )
    {
        ++job->count;
        job->total_size += (goffset)st.st_size;
        job->total_block_size += (st.st_blocks * st.st_blksize);

        /* NOTE: if job->dest_dev is 0, that means our destination
         * folder is not on native UNIX filesystem. Hence it's not
         * on the same device. Our st.st_dev will always be non-zero
         * since our file is on a native UNIX filesystem. */

        /* only descends into files on the same filesystem */
        if( job->flags & FM_DC_JOB_SAME_FS )
        {
            if( st.st_dev != job->dest_dev )
                return TRUE;
        }
        /* only descends into files on the different filesystem */
        else if( job->flags & FM_DC_JOB_PREPARE_MOVE )
        {
            if( st.st_dev == job->dest_dev )
                return TRUE;
        }
    }
    else
    {
        GError* err = g_error_new(G_IO_ERROR, g_io_error_from_errno(errno), "%s", g_strerror(errno));
        FmJobErrorAction act = fm_job_emit_error(FM_JOB(job), err, FM_JOB_ERROR_MILD);
        g_error_free(err);
        err = NULL;
        if(act == FM_JOB_RETRY)
            goto _retry_stat;
        return FALSE;
    }
    if(fm_job_is_cancelled(fmjob))
        return FALSE;

    if( S_ISDIR(st.st_mode) ) /* if it's a dir */
    {
        GDir* dir_ent = g_dir_open(path, 0, NULL);
        if(dir_ent)
        {
            const char* basename;
            while( !fm_job_is_cancelled(fmjob)
                    && (basename = g_dir_read_name(dir_ent)) )
            {
                FmPath* sub = fm_path_new_child(fm_path, basename);
                if(!fm_job_is_cancelled(fmjob))
                {
                    if(deep_count_posix(job, sub))
                    {
                        /* for moving across different devices, an additional 'delete'
                         * for source file is needed. so let's +1 for the delete.*/
                        if(job->flags & FM_DC_JOB_PREPARE_MOVE)
                        {
                            ++job->total_size;
                            ++job->total_block_size;
                            ++job->count;
                        }
                    }
                }
                fm_path_unref(sub);
            }
            g_dir_close(dir_ent);
        }
    }
    g_free(path);
    return TRUE;
}