Restoring files from Recycle Bin/Trash

Hi, ALL,

Is there an API that will allow me to do the subj?

Thank you.

There’s no API for directly restoring a file in GIO, but you can get the original path for a trashed file using:

g_autoptr (GError) error = NULL;
g_autoptr (GFileInfo) info =
  g_file_query_info (trashed_file, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH,
                     G_FILE_QUERY_INFO_NONE,
                     NULL, &error);

g_autofree char *orig_path =
  g_file_info_get_attribute_as_string (info, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH);

g_autoptr (GFile) target =
  g_file_new_for_commandline_arg (orig_path);

Emmanuelle,
I presume I don’t have to free those pointers, right?

And then I can just copy the file from `/Trash to that location?

Thank you.

Only if you don’t use g_autofree and g_autoptr; otherwise: yes, you do have to manage the memory. Please, refer to the documentation to know the ownership transfer rules for the API.

Yes.

Emmanuelle,

Anything else needs to be done?

Thank you.

Not that I’m aware. Just make sure that the original location is using the trash:// URI scheme, so that the GVFS daemon will take care of removing the associated metadata when moving the file out of the trash.

Emmanuelle,

One more question - what do I need to do to get a list of files in a trash?

That way I can check what file to restore…

Thank you.

g_file_enumerate_children (g_file_new_for_uri ("trash:///"), ...) is what you are looking for…

Emmanuelle,

I use following:

bool res = true;
g_autoptr (GError) error = nullptr;
GFile *f = g_file_new_for_path( "trash:///" + path.c_str() );
g_autoptr (GFileInfo) info = g_file_query_info( f, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH, G_FILE_QUERY_INFO_NONE, NULL, &error );

The “f” pointer is valid, but the “info” pointer is NULL.

The error message I receive from the error reads:

Error when getting information for file “/home/igor/<my_prog>/trash:/ffileinstream.test”: No such file or directory

which is obviously incorrect, ass I would expect the directory to be:

~/Trash

or something to that extent.

What am I missing?

Thank you.

Emmanuelle,
Using g_file_new_for_uri(), instead of g_file_new_for_path() fixes it.

However looking at the documentation I’m a little confused.

Docs for g_file_move() says:

Tries to move the file or directory source to the location specified by destination. If native move operations are supported then this is used, otherwise a copy + delete fallback is used. The native implementation may support moving directories (for instance on moves inside the same filesystem), but the fallback code does not.

which implies that it is possible to move files and/or (non-empty) directory.

However, the documentation for g_file_copy() says:

Copies the file source to the location specified by destination. Can not handle recursive copies of directories.

which implies that files(s) and empty directory can be copied.

This is a little confusing.

I’d like to call g_file_move(), but is there a guarantee that both empty and non-empty dirs will be moved.

I’m going to try and test this, but it would be nice if the documentation will mention something in this regards.

Thank you.

This is a technical limitation. Consider the case of moving a directory to another directory within that filesystem. The operating system doesn’t have to actually move the files, it can just move the “pointer” to the file to another folder, whereas the actual file data stays in the same place on the physical disks. This is a very fast operation.

If you are moving between filesystems then this is not possible, the only way you can implement “move” is to copy the files over and then delete the original. If you are making this as part of a GUI option, maybe you might want to detect this condition and display it as a warning to the user that the move operation will be slow. To implement this copy fallback you can test for a G_IO_ERROR_NOT_SUPPORTED error from g_file_move.

@jfrancis
I understand the first part and the second part.

Problem is - what I’d like to do is to implement trashing file/folder and then restore file/folder independently, whether the folder contains files or it was just an empty folder. And so it is not very clear how to do that if I should recover the directory.

In a simple case:

I trashed the directory from ~/my_program and the directory is called “test”. The directory is not empty and contains one file test_file.

Now trying the following code:

GFile *f = g_file_new_for_uri( "trash:///" + path.c_str() );
g_autoptr (GFileInfo) info = g_file_query_info( f, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH, G_FILE_QUERY_INFO_NONE, NULL, error );

I get:

p (char *) g_file_get_parse_name( f )
“trash:////home/igor/my_prog/test”
p pat
“/home/igor/my_prog/test”.

And so trying to get the information fails.

Restoring the file doesn’t have this problem.

So even with the smallest and easiest case it doesn’t work.

Now when start talking “copy + delete” as a fallback for the directory - this is impossible to implement because if the directory is not empty the "g_file_copy() call will fail (based on the documentation).

Thank you.

Your leaking f, you may want to use g_autoptr here as well

Yes, I think you would have to copy each file one by one with g_file_enumerate. This has issues in that it is not atomic, but this is a necessary limitation if moving between filesystems. Maybe you could show a warning “this file is being restored to a different filesystem and will be slow” or similar. Or you could just fail the operation and tell the user that it is not supported for that file, possibly let them pick a new directory to restore to that is in that filesystem. It may be that this situation only comes up in the rare case where the user mounts a new filesystem in a subdirectory of an old filesystem.

Maybe it could also be worth checking how some other programs handle this edge case, e.g. Nautilus or similar.

@zbrown ,
I posted only relevant code. :wink:

Thank you.

@jfrancis ,
My understanding that this will happen even if it will go inside one FS.
Documentation on g_file_copy() does not differentiate on this.

But my question is actually about something else.
Why the code I posted works for files but doesn’t work for directories?
Should I use something else in this case? If yes - what?

Thank you.

Right, but you should be able to call g_file_move first to test if it’s possible to move.

One reason it may not be working is that you don’t want to put the full path after trash://. It should just be trash:///test. I just tried that with a directory and it seemed to work. You can test out the paths by using the gio command line tool to verify that everything is in place.

@jfrancis ,
Yes, thank you.

Also ust to clear any confusion - I have a directory with one file inside and it was successfully restored/moved back to the original place with the code above.

So I guess the only thing left to test is the cross-platform deletion (well, cross-filesystem). But my program is not meant to run from the other FS.

Thank you for the help, everyone.