Moved trash logic to plugin structure

This commit is contained in:
2022-10-01 16:04:46 -05:00
parent d3e42b3ae0
commit da63e6e44e
13 changed files with 556 additions and 472 deletions

View File

@@ -0,0 +1,3 @@
"""
Pligin Module
"""

View File

@@ -0,0 +1,3 @@
"""
Pligin Package
"""

View File

@@ -0,0 +1,12 @@
{
"manifest": {
"name": "Trasher",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"requests": {
"ui_target": "context_menu",
"pass_fm_events": "true"
}
}
}

115
plugins/trasher/plugin.py Normal file
View File

@@ -0,0 +1,115 @@
# Python imports
import os, threading, subprocess, inspect
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib, Gio
# Application imports
from plugins.plugin_base import PluginBase
from .xdgtrash import XDGTrash
# NOTE: Threads WILL NOT die with parent's destruction.
def threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start()
return wrapper
# NOTE: Threads WILL die with parent's destruction.
def daemon_threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
return wrapper
class Plugin(PluginBase):
def __init__(self):
super().__init__()
self.path = os.path.dirname(os.path.realpath(__file__))
self._GLADE_FILE = f"{self.path}/trasher.glade"
self.name = "Trasher" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
# where self.name should not be needed for message comms
self.trashman = XDGTrash()
self.trash_files_path = f"{GLib.get_user_data_dir()}/Trash/files"
self.trash_info_path = f"{GLib.get_user_data_dir()}/Trash/info"
self.trashman.regenerate()
def get_ui_element(self):
self._builder = Gtk.Builder()
self._builder.add_from_file(self._GLADE_FILE)
classes = [self]
handlers = {}
for c in classes:
methods = None
try:
methods = inspect.getmembers(c, predicate=inspect.ismethod)
handlers.update(methods)
except Exception as e:
print(repr(e))
self._builder.connect_signals(handlers)
trasher = self._builder.get_object("trasher")
trasher.show_all()
return trasher
def run(self):
self._event_system.subscribe("show_trash_buttons", self._show_trash_buttons)
self._event_system.subscribe("hide_trash_buttons", self._hide_trash_buttons)
def _show_trash_buttons(self):
self._builder.get_object("restore_from_trash").show()
self._builder.get_object("empty_trash").show()
def _hide_trash_buttons(self):
self._builder.get_object("restore_from_trash").hide()
self._builder.get_object("empty_trash").hide()
def delete_files(self, widget = None, eve = None):
self._event_system.emit("get_current_state")
state = self._fm_state
uris = state.selected_files
response = None
state.warning_alert.format_secondary_text(f"Do you really want to delete the {len(uris)} file(s)?")
for uri in uris:
file = Gio.File.new_for_path(uri)
if not response:
response = state.warning_alert.run()
state.warning_alert.hide()
if response == Gtk.ResponseType.YES:
type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
if type == Gio.FileType.DIRECTORY:
state.tab.delete_file( file.get_path() )
else:
file.delete(cancellable=None)
else:
break
def trash_files(self, widget = None, eve = None, verbocity = False):
self._event_system.emit("get_current_state")
state = self._fm_state
for uri in state.selected_files:
self.trashman.trash(uri, verbocity)
def restore_trash_files(self, widget = None, eve = None, verbocity = False):
self._event_system.emit("get_current_state")
state = self._fm_state
for uri in state.selected_files:
self.trashman.restore(filename=uri.split("/")[-1], verbose = verbocity)
def empty_trash(self, widget = None, eve = None, verbocity = False):
self.trashman.empty(verbose = verbocity)
def go_to_trash(self, widget = None, eve = None, verbocity = False):
self._event_system.emit("go_to_path", self.trash_files_path)

46
plugins/trasher/trash.py Executable file
View File

@@ -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 + self.size_dir(item)
return size
def regenerate(self):
"""Regenerate the trash and recreate metadata."""
pass # Some backends dont need regeneration.
def empty(self, verbose):
"""Empty the trash."""
raise NotImplementedError(_('Backend didnt implement this functionality'))
def list(self, human=True):
"""List the trash contents."""
raise NotImplementedError(_('Backend didnt implement this functionality'))
def trash(self, filepath, verbose):
"""Move specified file to trash."""
raise NotImplementedError(_('Backend didnt implement this functionality'))
def restore(self, filename, verbose):
"""Restore a file from trash."""
raise NotImplementedError(_('Backend didnt \ implement this functionality'))

View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.16"/>
<object class="GtkImage" id="trash_img">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">user-trash</property>
</object>
<object class="GtkImage" id="trash_img2">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">user-trash</property>
</object>
<object class="GtkExpander" id="trasher">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin-top">5</property>
<property name="margin-bottom">10</property>
<property name="label-fill">True</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkButton" id="restore_from_trash">
<property name="label" translatable="yes">Restore From Trash</property>
<property name="name">restore_from_trash</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Restore From Trash...</property>
<signal name="button-release-event" handler="restore_trash_files" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="empty_trash">
<property name="label" translatable="yes">Empty Trash</property>
<property name="name">empty_trash</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Empty Trash...</property>
<signal name="button-release-event" handler="empty_trash" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Trash</property>
<property name="name">trash</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Move to Trash...</property>
<property name="margin-top">20</property>
<property name="image">trash_img</property>
<property name="always-show-image">True</property>
<signal name="button-release-event" handler="trash_files" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Go To Trash</property>
<property name="name">go_to_trash</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Go To Trash...</property>
<property name="image">trash_img2</property>
<property name="always-show-image">True</property>
<signal name="button-release-event" handler="go_to_trash" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">gtk-delete</property>
<property name="name">delete</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Delete...</property>
<property name="margin-top">20</property>
<property name="use-stock">True</property>
<property name="always-show-image">True</property>
<signal name="button-release-event" handler="delete_files" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">False</property>
<property name="label" translatable="yes">Trash</property>
<property name="justify">center</property>
</object>
</child>
</object>
</interface>

161
plugins/trasher/xdgtrash.py Executable file
View File

@@ -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('Couldnt 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'))