Add "clear trash", "restore from trash" options.
This commit is contained in:
parent
8196102a2b
commit
7262e63ddc
|
@ -13,7 +13,6 @@ sudo apt-get install python3 wget ffmpegthumbnailer steamcmd
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
<ul>
|
<ul>
|
||||||
<li>Add "clear trash", "restore from trash" options.</li>
|
|
||||||
<li>Add simpleish plugin system to run bash/python scripts.</li>
|
<li>Add simpleish plugin system to run bash/python scripts.</li>
|
||||||
<li>Add DnD context awareness for over folder drop.</li>
|
<li>Add DnD context awareness for over folder drop.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -25,7 +25,7 @@ class Settings:
|
||||||
REMUX_FOLDER = USER_HOME + "/.remuxs" # Remuxed files folder
|
REMUX_FOLDER = USER_HOME + "/.remuxs" # Remuxed files folder
|
||||||
|
|
||||||
STEAM_BASE_URL = "https://steamcdn-a.akamaihd.net/steam/apps/"
|
STEAM_BASE_URL = "https://steamcdn-a.akamaihd.net/steam/apps/"
|
||||||
ICON_DIRS = ["/usr/share/pixmaps", "/usr/share/icons", USER_HOME + "/.icons" ,]
|
ICON_DIRS = ["/usr/share/pixmaps", "/usr/share/icons", f"{USER_HOME}/.icons" ,]
|
||||||
BASE_THUMBS_PTH = USER_HOME + "/.thumbnails" # Used for thumbnail generation
|
BASE_THUMBS_PTH = USER_HOME + "/.thumbnails" # Used for thumbnail generation
|
||||||
ABS_THUMBS_PTH = BASE_THUMBS_PTH + "/normal" # Used for thumbnail generation
|
ABS_THUMBS_PTH = BASE_THUMBS_PTH + "/normal" # Used for thumbnail generation
|
||||||
STEAM_ICONS_PTH = BASE_THUMBS_PTH + "/steam_icons"
|
STEAM_ICONS_PTH = BASE_THUMBS_PTH + "/steam_icons"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Python imports
|
# Python imports
|
||||||
import sys, traceback, threading, subprocess, signal, inspect, os, time
|
import sys, traceback, threading, signal, inspect, os, time
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
import gi
|
||||||
|
@ -153,6 +153,10 @@ class Controller(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin, \
|
||||||
self.trash_files()
|
self.trash_files()
|
||||||
if action == "go_to_trash":
|
if action == "go_to_trash":
|
||||||
self.builder.get_object("path_entry").set_text(self.trash_files_path)
|
self.builder.get_object("path_entry").set_text(self.trash_files_path)
|
||||||
|
if action == "restore_from_trash":
|
||||||
|
self.restore_trash_files()
|
||||||
|
if action == "empty_trash":
|
||||||
|
self.empty_trash()
|
||||||
|
|
||||||
|
|
||||||
if action == "create":
|
if action == "create":
|
||||||
|
|
|
@ -5,6 +5,7 @@ from gi.repository import GLib
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from shellfm import WindowController
|
from shellfm import WindowController
|
||||||
|
from trasher.xdgtrash import XDGTrash
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,8 +16,10 @@ class Controller_Data:
|
||||||
|
|
||||||
def setup_controller_data(self):
|
def setup_controller_data(self):
|
||||||
self.window_controller = WindowController()
|
self.window_controller = WindowController()
|
||||||
self.state = self.window_controller.load_state()
|
self.trashman = XDGTrash()
|
||||||
|
self.trashman.regenerate()
|
||||||
|
|
||||||
|
self.state = self.window_controller.load_state()
|
||||||
self.builder = self.settings.builder
|
self.builder = self.settings.builder
|
||||||
self.logger = self.settings.logger
|
self.logger = self.settings.logger
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,20 @@ class WidgetFileActionMixin:
|
||||||
num /= 1024.0
|
num /= 1024.0
|
||||||
return f"{num:.1f} Yi{suffix}"
|
return f"{num:.1f} Yi{suffix}"
|
||||||
|
|
||||||
|
def get_dir_size(self, sdir):
|
||||||
|
"""Get the size of a directory. Based on code found online."""
|
||||||
|
size = os.path.getsize(sdir)
|
||||||
|
|
||||||
|
for item in os.listdir(sdir):
|
||||||
|
item = os.path.join(sdir, item)
|
||||||
|
|
||||||
|
if os.path.isfile(item):
|
||||||
|
size = size + os.path.getsize(item)
|
||||||
|
elif os.path.isdir(item):
|
||||||
|
size = size + self.get_dir_size(item)
|
||||||
|
|
||||||
|
return size
|
||||||
|
|
||||||
|
|
||||||
def set_file_watcher(self, view):
|
def set_file_watcher(self, view):
|
||||||
if view.get_dir_watcher():
|
if view.get_dir_watcher():
|
||||||
|
@ -196,10 +210,16 @@ class WidgetFileActionMixin:
|
||||||
wid, tid, view, iconview, store = self.get_current_state()
|
wid, tid, view, iconview, store = self.get_current_state()
|
||||||
uris = self.format_to_uris(store, wid, tid, self.selected_files, True)
|
uris = self.format_to_uris(store, wid, tid, self.selected_files, True)
|
||||||
for uri in uris:
|
for uri in uris:
|
||||||
file = Gio.File.new_for_path(uri)
|
self.trashman.trash(uri, False)
|
||||||
file.trash(cancellable=None)
|
|
||||||
|
|
||||||
|
def restore_trash_files(self):
|
||||||
|
wid, tid, view, iconview, store = self.get_current_state()
|
||||||
|
uris = self.format_to_uris(store, wid, tid, self.selected_files, True)
|
||||||
|
for uri in uris:
|
||||||
|
self.trashman.restore(filename=uri.split("/")[-1], verbose=False)
|
||||||
|
|
||||||
|
def empty_trash(self):
|
||||||
|
self.trashman.empty(verbose=False)
|
||||||
|
|
||||||
|
|
||||||
def create_files(self):
|
def create_files(self):
|
||||||
|
|
|
@ -81,11 +81,19 @@ class WindowMixin(TabMixin):
|
||||||
def set_bottom_labels(self, view):
|
def set_bottom_labels(self, view):
|
||||||
_wid, _tid, _view, iconview, store = self.get_current_state()
|
_wid, _tid, _view, iconview, store = self.get_current_state()
|
||||||
selected_files = iconview.get_selected_items()
|
selected_files = iconview.get_selected_items()
|
||||||
path_file = Gio.File.new_for_path( view.get_current_directory())
|
current_directory = view.get_current_directory()
|
||||||
|
path_file = Gio.File.new_for_path( current_directory)
|
||||||
mount_file = path_file.query_filesystem_info(attributes="filesystem::*", cancellable=None)
|
mount_file = path_file.query_filesystem_info(attributes="filesystem::*", cancellable=None)
|
||||||
formatted_mount_free = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) )
|
formatted_mount_free = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) )
|
||||||
formatted_mount_size = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::size")) )
|
formatted_mount_size = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::size")) )
|
||||||
|
|
||||||
|
if self.trash_files_path == current_directory:
|
||||||
|
self.builder.get_object("restore_from_trash").show()
|
||||||
|
self.builder.get_object("empty_trash").show()
|
||||||
|
else:
|
||||||
|
self.builder.get_object("restore_from_trash").hide()
|
||||||
|
self.builder.get_object("empty_trash").hide()
|
||||||
|
|
||||||
# If something selected
|
# If something selected
|
||||||
self.bottom_size_label.set_label(f"{formatted_mount_free} free / {formatted_mount_size}")
|
self.bottom_size_label.set_label(f"{formatted_mount_free} free / {formatted_mount_size}")
|
||||||
self.bottom_path_label.set_label(view.get_current_directory())
|
self.bottom_path_label.set_label(view.get_current_directory())
|
||||||
|
@ -94,7 +102,7 @@ class WindowMixin(TabMixin):
|
||||||
combined_size = 0
|
combined_size = 0
|
||||||
for uri in uris:
|
for uri in uris:
|
||||||
file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::size",
|
file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::size",
|
||||||
flags=Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
|
flags=Gio.FileQueryInfoFlags.NONE,
|
||||||
cancellable=None)
|
cancellable=None)
|
||||||
file_size = file_info.get_size()
|
file_size = file_info.get_size()
|
||||||
combined_size += file_size
|
combined_size += file_size
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Python imports
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Trash(object):
|
||||||
|
"""Base Trash class."""
|
||||||
|
|
||||||
|
def size_dir(self, sdir):
|
||||||
|
"""Get the size of a directory. Based on code found online."""
|
||||||
|
size = os.path.getsize(sdir)
|
||||||
|
|
||||||
|
for item in os.listdir(sdir):
|
||||||
|
item = os.path.join(sdir, item)
|
||||||
|
|
||||||
|
if os.path.isfile(item):
|
||||||
|
size = size + os.path.getsize(item)
|
||||||
|
elif os.path.isdir(item):
|
||||||
|
size = size + size_dir(item)
|
||||||
|
|
||||||
|
return size
|
||||||
|
|
||||||
|
def regenerate(self):
|
||||||
|
"""Regenerate the trash and recreate metadata."""
|
||||||
|
pass # Some backends don’t need regeneration.
|
||||||
|
|
||||||
|
def empty(self, verbose):
|
||||||
|
"""Empty the trash."""
|
||||||
|
raise NotImplementedError(_('Backend didn’t implement this functionality'))
|
||||||
|
|
||||||
|
def list(self, human=True):
|
||||||
|
"""List the trash contents."""
|
||||||
|
raise NotImplementedError(_('Backend didn’t implement this functionality'))
|
||||||
|
|
||||||
|
def trash(self, filepath, verbose):
|
||||||
|
"""Move specified file to trash."""
|
||||||
|
raise NotImplementedError(_('Backend didn’t implement this functionality'))
|
||||||
|
|
||||||
|
def restore(self, filename, verbose):
|
||||||
|
"""Restore a file from trash."""
|
||||||
|
raise NotImplementedError(_('Backend didn’t \ implement this functionality'))
|
|
@ -0,0 +1,161 @@
|
||||||
|
from .trash import Trash
|
||||||
|
import shutil
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import datetime
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
|
try:
|
||||||
|
import configparser
|
||||||
|
except ImportError:
|
||||||
|
import ConfigParser as configparser
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class XDGTrash(Trash):
|
||||||
|
"""XDG trash backend."""
|
||||||
|
def __init__(self):
|
||||||
|
self.trashdir = None
|
||||||
|
self.filedir = None
|
||||||
|
self.infodir = None
|
||||||
|
|
||||||
|
if os.getenv('XDG_DATA_HOME') is None:
|
||||||
|
self.trashdir = os.path.expanduser('~/.local/share/Trash')
|
||||||
|
else:
|
||||||
|
self.trashdir = os.getenv('XDG_DATA_HOME') + '/Trash'
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not os.path.exists(self.trashdir):
|
||||||
|
os.mkdir(self.trashdir)
|
||||||
|
except OSError:
|
||||||
|
self.trashdir = os.path.join('tmp' 'TRASH')
|
||||||
|
raise('Couldn’t access the proper directory, temporary trash is in in /tmp/TRASH')
|
||||||
|
|
||||||
|
self.filedir = self.trashdir + '/files/'
|
||||||
|
self.infodir = self.trashdir + '/info/'
|
||||||
|
|
||||||
|
def regenerate(self):
|
||||||
|
"""Regenerate the trash and recreate metadata."""
|
||||||
|
print('Regenerating the trash and recreating metadata...')
|
||||||
|
zerosize = False
|
||||||
|
|
||||||
|
if not os.path.exists(self.trashdir):
|
||||||
|
os.mkdir(self.trashdir)
|
||||||
|
zerosize = True
|
||||||
|
|
||||||
|
if ((not os.path.exists(self.filedir)) or
|
||||||
|
(not os.path.exists(self.infodir))):
|
||||||
|
os.mkdir(self.filedir)
|
||||||
|
os.mkdir(self.infodir)
|
||||||
|
zerosize = True
|
||||||
|
if not zerosize:
|
||||||
|
trashsize = (self.size_dir(self.filedir) + self.size_dir(self.infodir))
|
||||||
|
else:
|
||||||
|
trashsize = 0
|
||||||
|
|
||||||
|
infofile = '[Cached]\nSize=' + str(trashsize) + '\n'
|
||||||
|
fh = open(os.path.join(self.trashdir, 'metadata'), 'w')
|
||||||
|
fh.write(infofile)
|
||||||
|
fh.close()
|
||||||
|
|
||||||
|
def empty(self, verbose):
|
||||||
|
"""Empty the trash."""
|
||||||
|
print('emptying (verbose={})'.format(verbose))
|
||||||
|
shutil.rmtree(self.filedir)
|
||||||
|
shutil.rmtree(self.infodir)
|
||||||
|
self.regenerate()
|
||||||
|
if verbose:
|
||||||
|
sys.stderr.write(_('emptied the trash\n'))
|
||||||
|
|
||||||
|
def list(self, human=True):
|
||||||
|
"""List the trash contents."""
|
||||||
|
if human:
|
||||||
|
print('listing contents (on stdout; human=True)')
|
||||||
|
else:
|
||||||
|
print('listing contents (return; human=False)')
|
||||||
|
dirs = []
|
||||||
|
files = []
|
||||||
|
for f in os.listdir(self.filedir):
|
||||||
|
if os.path.isdir(self.filedir + f):
|
||||||
|
dirs.append(f)
|
||||||
|
else:
|
||||||
|
files.append(f)
|
||||||
|
|
||||||
|
dirs.sort()
|
||||||
|
files.sort()
|
||||||
|
|
||||||
|
allfiles = []
|
||||||
|
for i in dirs:
|
||||||
|
allfiles.append(i + '/')
|
||||||
|
for i in files:
|
||||||
|
allfiles.append(i)
|
||||||
|
if human:
|
||||||
|
if allfiles != []:
|
||||||
|
print('\n'.join(allfiles))
|
||||||
|
else:
|
||||||
|
return allfiles
|
||||||
|
|
||||||
|
def trash(self, filepath, verbose):
|
||||||
|
"""Move specified file to trash."""
|
||||||
|
print('trashing file {} (verbose={})'.format(filepath, verbose))
|
||||||
|
# Filename alteration, a big mess.
|
||||||
|
filename = os.path.basename(filepath)
|
||||||
|
fileext = os.path.splitext(filename)
|
||||||
|
|
||||||
|
tomove = filename
|
||||||
|
collision = True
|
||||||
|
i = 1
|
||||||
|
|
||||||
|
while collision:
|
||||||
|
if os.path.lexists(self.filedir + tomove):
|
||||||
|
tomove = fileext[0] + ' ' + str(i) + fileext[1]
|
||||||
|
i = i + 1
|
||||||
|
else:
|
||||||
|
collision = False
|
||||||
|
|
||||||
|
infofile = """[Trash Info]
|
||||||
|
Path={}
|
||||||
|
DeletionDate={}
|
||||||
|
""".format(os.path.realpath(filepath),
|
||||||
|
datetime.datetime.now().strftime('%Y-%m-%dT%H:%m:%S'))
|
||||||
|
|
||||||
|
os.rename(filepath, self.filedir + tomove)
|
||||||
|
|
||||||
|
f = open(os.path.join(self.infodir, tomove + '.trashinfo'), 'w')
|
||||||
|
f.write(infofile)
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
self.regenerate()
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
sys.stderr.write(_('trashed \'{}\'\n').format(filename))
|
||||||
|
|
||||||
|
def restore(self, filename, verbose, tocwd=False):
|
||||||
|
"""Restore a file from trash."""
|
||||||
|
print('restoring file {} (verbose={}, tocwd={})'.format(filename, verbose, tocwd))
|
||||||
|
info = configparser.ConfigParser()
|
||||||
|
if os.path.exists(os.path.join(self.filedir, filename)):
|
||||||
|
info.read(os.path.join(self.infodir, filename + '.trashinfo'))
|
||||||
|
restname = os.path.basename(info.get('Trash Info', 'Path'))
|
||||||
|
|
||||||
|
if tocwd:
|
||||||
|
restdir = os.path.abspath('.')
|
||||||
|
else:
|
||||||
|
restdir = os.path.dirname(info.get('Trash Info', 'Path'))
|
||||||
|
|
||||||
|
restfile = os.path.join(restdir, restname)
|
||||||
|
if not os.path.exists(restdir):
|
||||||
|
raise TMError('restore', 'nodir', _('no such directory: {}'
|
||||||
|
' -- cannot restore').format(restdir))
|
||||||
|
os.rename(os.path.join(self.filedir, filename), restfile)
|
||||||
|
os.remove(os.path.join(self.infodir, filename + '.trashinfo'))
|
||||||
|
self.regenerate()
|
||||||
|
print('restored {} to {}'.format(filename, restfile))
|
||||||
|
if verbose:
|
||||||
|
sys.stderr.write(_('restored {} to {}\n').format(filename, restfile))
|
||||||
|
|
||||||
|
else:
|
||||||
|
print('couldn\'t find {} in trash'.format(filename))
|
||||||
|
raise TMError('restore', 'nofile', _('no such file in trash'))
|
Loading…
Reference in New Issue