Compare commits
	
		
			1 Commits
		
	
	
		
			c01e81af27
			...
			convert-to
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 09c5af3821 | 
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +1,3 @@ | |||||||
| docs/ |  | ||||||
| .idea/ |  | ||||||
| *.zip |  | ||||||
|  |  | ||||||
| # ---> Python | # ---> Python | ||||||
| # Byte-compiled / optimized / DLL files | # Byte-compiled / optimized / DLL files | ||||||
| __pycache__/ | __pycache__/ | ||||||
| @@ -141,3 +137,4 @@ dmypy.json | |||||||
|  |  | ||||||
| # Cython debug symbols | # Cython debug symbols | ||||||
| cython_debug/ | cython_debug/ | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | # SolarFM | ||||||
|  |  | ||||||
| # SolarFM | # SolarFM | ||||||
| SolarFM is a Gtk+ Python file manager. | SolarFM is a Gtk+ Python file manager. | ||||||
|  |  | ||||||
| @@ -11,8 +13,8 @@ sudo apt-get install python3.8 wget python3-setproctitle python3-gi ffmpegthumbn | |||||||
|  |  | ||||||
| # TODO | # TODO | ||||||
| <ul> | <ul> | ||||||
| <li>Add simpleish preview plugin for various file types.</li> | <li>Add simpleish plugin system to run bash/python scripts.</li> | ||||||
| <li>Add simpleish bulk-renamer.</li> | <li>Add DnD context awareness for over folder drop.</li> | ||||||
| </ul> | </ul> | ||||||
|  |  | ||||||
| # Images | # Images | ||||||
|   | |||||||
										
											Binary file not shown.
										
									
								
							| @@ -1,42 +0,0 @@ | |||||||
| ### Note |  | ||||||
| Copy the example and rename it to your desired name. Plugins define a ui target slot with the 'ui_target' requests data but don't have to if not directly interacted with. |  | ||||||
| Plugins must have a run method defined; though, you do not need to necessarily do anything within it. The run method implies that the passed in event system or other data is ready for the plugin to use. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ### Manifest Example (All are required even if empty.) |  | ||||||
| ``` |  | ||||||
| class Manifest: |  | ||||||
|     name: str     = "Example Plugin" |  | ||||||
|     author: str   = "John Doe" |  | ||||||
|     version: str  = "0.0.1" |  | ||||||
|     support: str  = "" |  | ||||||
|     requests: {}  = { |  | ||||||
|         'ui_target': "plugin_control_list", |  | ||||||
|         'pass_fm_events': "true" |  | ||||||
|     } |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ### Requests |  | ||||||
| ``` |  | ||||||
| requests: {}  = { |  | ||||||
|     'ui_target': "plugin_control_list", |  | ||||||
|     'ui_target_id': "<some other Gtk Glade ID>",          # Only needed if using "other" in "ui_target". See below for predefined "ui_target" options... |  | ||||||
|     'pass_fm_events': "true",                             # If empty or not present will be ignored. |  | ||||||
|     "pass_ui_objects": [""],                              # Request reference to a UI component. Will be passed back as array to plugin. |  | ||||||
|     'bind_keys': [f"{name}||send_message:<Control>f"], |  | ||||||
|                   f"{name}||do_save:<Control>s"]          # Bind keys with method and key pare using list. Must pass "name" like shown with delimiter to its right. |  | ||||||
|  |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| UI Targets: |  | ||||||
| <ul> |  | ||||||
| <li>main_Window</li> |  | ||||||
| <li>main_menu_bar</li> |  | ||||||
| <li>path_menu_bar</li> |  | ||||||
| <li>plugin_control_list</li> |  | ||||||
| <li>window_(1-4)</li> |  | ||||||
| <li>context_menu</li> |  | ||||||
| <li>other</li> |  | ||||||
| </ul> |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Module |  | ||||||
| """ |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Package |  | ||||||
| """ |  | ||||||
| @@ -1,164 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <!-- Generated with glade 3.38.2 --> |  | ||||||
| <interface> |  | ||||||
|   <requires lib="gtk+" version="3.16"/> |  | ||||||
|   <object class="GtkTextBuffer" id="arc_command_buffer"> |  | ||||||
|     <property name="text" translatable="yes">$(which 7za || echo 7zr) a %o %N</property> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkFileChooserDialog" id="archiver_dialogue"> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="modal">True</property> |  | ||||||
|     <property name="window-position">center</property> |  | ||||||
|     <property name="type-hint">dialog</property> |  | ||||||
|     <property name="gravity">center</property> |  | ||||||
|     <property name="do-overwrite-confirmation">True</property> |  | ||||||
|     <property name="select-multiple">True</property> |  | ||||||
|     <child internal-child="vbox"> |  | ||||||
|       <object class="GtkBox"> |  | ||||||
|         <property name="can-focus">False</property> |  | ||||||
|         <property name="orientation">vertical</property> |  | ||||||
|         <property name="spacing">2</property> |  | ||||||
|         <child internal-child="action_area"> |  | ||||||
|           <object class="GtkButtonBox"> |  | ||||||
|             <property name="can-focus">False</property> |  | ||||||
|             <property name="layout-style">end</property> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkButton" id="button21"> |  | ||||||
|                 <property name="label">gtk-cancel</property> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="receives-default">True</property> |  | ||||||
|                 <property name="use-stock">True</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">True</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">0</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkButton" id="button22"> |  | ||||||
|                 <property name="label">gtk-ok</property> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="receives-default">True</property> |  | ||||||
|                 <property name="use-stock">True</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">True</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">1</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|           <packing> |  | ||||||
|             <property name="expand">False</property> |  | ||||||
|             <property name="fill">False</property> |  | ||||||
|             <property name="position">0</property> |  | ||||||
|           </packing> |  | ||||||
|         </child> |  | ||||||
|         <child> |  | ||||||
|           <object class="GtkBox"> |  | ||||||
|             <property name="visible">True</property> |  | ||||||
|             <property name="can-focus">False</property> |  | ||||||
|             <property name="orientation">vertical</property> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkBox"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="homogeneous">True</property> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkLabel"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="label" translatable="yes">Compress Commands:</property> |  | ||||||
|                     <property name="xalign">0.20000000298023224</property> |  | ||||||
|                     <attributes> |  | ||||||
|                       <attribute name="gravity" value="west"/> |  | ||||||
|                     </attributes> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">False</property> |  | ||||||
|                     <property name="fill">True</property> |  | ||||||
|                     <property name="position">0</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <placeholder/> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkLabel"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="label" translatable="yes">Archive Format:</property> |  | ||||||
|                     <property name="xalign">1</property> |  | ||||||
|                     <attributes> |  | ||||||
|                       <attribute name="gravity" value="east"/> |  | ||||||
|                     </attributes> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">False</property> |  | ||||||
|                     <property name="fill">True</property> |  | ||||||
|                     <property name="position">2</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkComboBoxText"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="active">0</property> |  | ||||||
|                     <property name="active-id">0</property> |  | ||||||
|                     <items> |  | ||||||
|                       <item id="0" translatable="yes">7Zip (*.7z)</item> |  | ||||||
|                       <item id="1" translatable="yes">Zip (*.zip *.ZIP)</item> |  | ||||||
|                       <item id="2" translatable="yes">RAR (*.rar *.RAR)</item> |  | ||||||
|                       <item id="3" translatable="yes">Tar (*.tar)</item> |  | ||||||
|                       <item id="4" translatable="yes">Tar bzip2 (*.tar.bz2)</item> |  | ||||||
|                       <item id="5" translatable="yes">Tar Gzip (*.tar.gz *.tgz)</item> |  | ||||||
|                       <item id="6" translatable="yes">Tar xz (*.tar.xz *.txz)</item> |  | ||||||
|                       <item id="7" translatable="yes">Gzip (*.gz)</item> |  | ||||||
|                       <item id="8" translatable="yes">XZ (*.xz)</item> |  | ||||||
|                     </items> |  | ||||||
|                     <signal name="changed" handler="set_arc_buffer_text" swapped="no"/> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">False</property> |  | ||||||
|                     <property name="fill">True</property> |  | ||||||
|                     <property name="position">3</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">False</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">0</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkTextView" id="arc_command"> |  | ||||||
|                 <property name="height-request">72</property> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="buffer">arc_command_buffer</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">True</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">1</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|           <packing> |  | ||||||
|             <property name="expand">False</property> |  | ||||||
|             <property name="fill">True</property> |  | ||||||
|             <property name="position">2</property> |  | ||||||
|           </packing> |  | ||||||
|         </child> |  | ||||||
|       </object> |  | ||||||
|     </child> |  | ||||||
|     <action-widgets> |  | ||||||
|       <action-widget response="-6">button21</action-widget> |  | ||||||
|       <action-widget response="-5">button22</action-widget> |  | ||||||
|     </action-widgets> |  | ||||||
|   </object> |  | ||||||
| </interface> |  | ||||||
| @@ -1,12 +0,0 @@ | |||||||
| { |  | ||||||
|     "manifest": { |  | ||||||
|         "name": "Archiver", |  | ||||||
|         "author": "ITDominator", |  | ||||||
|         "version": "0.0.1", |  | ||||||
|         "support": "", |  | ||||||
|         "requests": { |  | ||||||
|             "ui_target": "context_menu_plugins", |  | ||||||
|             "pass_fm_events": "true" |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,129 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os |  | ||||||
| import threading |  | ||||||
| import subprocess |  | ||||||
| import inspect |  | ||||||
| import shlex |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from plugins.plugin_base import PluginBase |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # 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}/archiver.glade" |  | ||||||
|         self.name = "Archiver"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus |  | ||||||
|                                 #       where self.name should not be needed for message comms |  | ||||||
|         self._archiver_dialogue  = None |  | ||||||
|         self._arc_command_buffer = None |  | ||||||
|  |  | ||||||
|         # In compress commands: |  | ||||||
|         #    %n: First selected filename/dir to archive |  | ||||||
|         #    %N: All selected filenames/dirs to archive, or (with %O) a single filename |  | ||||||
|         #    %o: Resulting single archive file |  | ||||||
|         #    %O: Resulting archive per source file/directory (use changes %N meaning) |  | ||||||
|         # |  | ||||||
|         #  In extract commands: |  | ||||||
|         #    %x: Archive file to extract |  | ||||||
|         #    %g: Unique extraction target filename with optional subfolder |  | ||||||
|         #    %G: Unique extraction target filename, never with subfolder |  | ||||||
|         # |  | ||||||
|         #  In list commands: |  | ||||||
|         #      %x: Archive to list |  | ||||||
|         # |  | ||||||
|         #  Plus standard bash variables are accepted. |  | ||||||
|         self.arc_commands            = [ '$(which 7za || echo 7zr) a %o %N', |  | ||||||
|                                                                 'zip -r %o %N', |  | ||||||
|                                                                 'rar a -r %o %N', |  | ||||||
|                                                                 'tar -cvf %o %N', |  | ||||||
|                                                                 'tar -cvjf %o %N', |  | ||||||
|                                                                 'tar -cvzf %o %N', |  | ||||||
|                                                                 'tar -cvJf %o %N', |  | ||||||
|                                                                 'gzip -c %N > %O', |  | ||||||
|                                                                 'xz -cz %N > %O' |  | ||||||
|                                         ] |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def generate_reference_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) |  | ||||||
|  |  | ||||||
|         self._archiver_dialogue  = self._builder.get_object("archiver_dialogue") |  | ||||||
|         self._arc_command_buffer = self._builder.get_object("arc_command_buffer") |  | ||||||
|  |  | ||||||
|         item = Gtk.ImageMenuItem(self.name) |  | ||||||
|         item.set_image( Gtk.Image(stock=Gtk.STOCK_FLOPPY) ) |  | ||||||
|         item.connect("activate", self.show_archiver_dialogue) |  | ||||||
|         item.set_always_show_image(True) |  | ||||||
|         return item |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def run(self): |  | ||||||
|         ... |  | ||||||
|  |  | ||||||
|     def show_archiver_dialogue(self, widget=None, eve=None): |  | ||||||
|         self._event_system.emit("get_current_state") |  | ||||||
|         state = self._fm_state |  | ||||||
|  |  | ||||||
|         self._archiver_dialogue.set_action(Gtk.FileChooserAction.SAVE) |  | ||||||
|         self._archiver_dialogue.set_current_folder(state.tab.get_current_directory()) |  | ||||||
|         self._archiver_dialogue.set_current_name("arc.7z") |  | ||||||
|  |  | ||||||
|         response = self._archiver_dialogue.run() |  | ||||||
|         if response == Gtk.ResponseType.OK: |  | ||||||
|             save_target = self._archiver_dialogue.get_filename() |  | ||||||
|             self.archive_files(save_target, state) |  | ||||||
|         if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|         self._archiver_dialogue.hide() |  | ||||||
|  |  | ||||||
|     def archive_files(self, save_target, state): |  | ||||||
|         paths       = [shlex.quote(p) for p in state.selected_files] |  | ||||||
|  |  | ||||||
|         sItr, eItr  = self._arc_command_buffer.get_bounds() |  | ||||||
|         pre_command = self._arc_command_buffer.get_text(sItr, eItr, False) |  | ||||||
|         pre_command = pre_command.replace("%o", shlex.quote(save_target)) |  | ||||||
|         pre_command = pre_command.replace("%N", ' '.join(paths)) |  | ||||||
|         command     = f"{state.tab.terminal_app} -e {shlex.quote(pre_command)}" |  | ||||||
|         current_dir = state.tab.get_current_directory() |  | ||||||
|  |  | ||||||
|         state.tab.execute(shlex.split(command), start_dir=shlex.quote(current_dir)) |  | ||||||
|  |  | ||||||
|     def set_arc_buffer_text(self, widget=None, eve=None): |  | ||||||
|         sid = widget.get_active_id() |  | ||||||
|         self._arc_command_buffer.set_text(self.arc_commands[int(sid)]) |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Module |  | ||||||
| """ |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Package |  | ||||||
| """ |  | ||||||
| @@ -1,129 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <!-- Generated with glade 3.40.0 --> |  | ||||||
| <interface> |  | ||||||
|   <requires lib="gtk+" version="3.24"/> |  | ||||||
|   <object class="GtkListStore" id="du_store"> |  | ||||||
|     <columns> |  | ||||||
|       <!-- column-name Size --> |  | ||||||
|       <column type="gchararray"/> |  | ||||||
|       <!-- column-name Dir --> |  | ||||||
|       <column type="gchararray"/> |  | ||||||
|     </columns> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkDialog" id="du_dialog"> |  | ||||||
|     <property name="width-request">420</property> |  | ||||||
|     <property name="height-request">450</property> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="modal">True</property> |  | ||||||
|     <property name="window-position">center</property> |  | ||||||
|     <property name="destroy-with-parent">True</property> |  | ||||||
|     <property name="type-hint">dialog</property> |  | ||||||
|     <property name="skip-taskbar-hint">True</property> |  | ||||||
|     <property name="skip-pager-hint">True</property> |  | ||||||
|     <property name="decorated">False</property> |  | ||||||
|     <property name="deletable">False</property> |  | ||||||
|     <property name="gravity">center</property> |  | ||||||
|     <child internal-child="vbox"> |  | ||||||
|       <object class="GtkBox"> |  | ||||||
|         <property name="can-focus">False</property> |  | ||||||
|         <property name="orientation">vertical</property> |  | ||||||
|         <property name="spacing">2</property> |  | ||||||
|         <child internal-child="action_area"> |  | ||||||
|           <object class="GtkButtonBox"> |  | ||||||
|             <property name="can-focus">False</property> |  | ||||||
|             <property name="layout-style">end</property> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkButton"> |  | ||||||
|                 <property name="label">gtk-close</property> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="receives-default">True</property> |  | ||||||
|                 <property name="use-stock">True</property> |  | ||||||
|                 <signal name="released" handler="_hide_du_menu" swapped="no"/> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">True</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">2</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|           <packing> |  | ||||||
|             <property name="expand">False</property> |  | ||||||
|             <property name="fill">False</property> |  | ||||||
|             <property name="position">0</property> |  | ||||||
|           </packing> |  | ||||||
|         </child> |  | ||||||
|         <child> |  | ||||||
|           <object class="GtkBox"> |  | ||||||
|             <property name="visible">True</property> |  | ||||||
|             <property name="can-focus">False</property> |  | ||||||
|             <property name="orientation">vertical</property> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkLabel" id="current_dir_lbl"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="margin-start">5</property> |  | ||||||
|                 <property name="margin-end">5</property> |  | ||||||
|                 <property name="margin-top">5</property> |  | ||||||
|                 <property name="margin-bottom">5</property> |  | ||||||
|                 <property name="label" translatable="yes">Current Directory:</property> |  | ||||||
|                 <property name="justify">center</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">False</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">0</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkScrolledWindow"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="shadow-type">in</property> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkTreeView"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">True</property> |  | ||||||
|                     <property name="model">du_store</property> |  | ||||||
|                     <property name="headers-clickable">False</property> |  | ||||||
|                     <child internal-child="selection"> |  | ||||||
|                       <object class="GtkTreeSelection"/> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkTreeViewColumn"> |  | ||||||
|                         <property name="title" translatable="yes">Disk Usage</property> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkCellRendererText"/> |  | ||||||
|                           <attributes> |  | ||||||
|                             <attribute name="text">0</attribute> |  | ||||||
|                           </attributes> |  | ||||||
|                         </child> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkCellRendererText"/> |  | ||||||
|                           <attributes> |  | ||||||
|                             <attribute name="text">1</attribute> |  | ||||||
|                           </attributes> |  | ||||||
|                         </child> |  | ||||||
|                       </object> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                 </child> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">True</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">1</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|           <packing> |  | ||||||
|             <property name="expand">True</property> |  | ||||||
|             <property name="fill">True</property> |  | ||||||
|             <property name="position">1</property> |  | ||||||
|           </packing> |  | ||||||
|         </child> |  | ||||||
|       </object> |  | ||||||
|     </child> |  | ||||||
|   </object> |  | ||||||
| </interface> |  | ||||||
| @@ -1,12 +0,0 @@ | |||||||
| { |  | ||||||
|     "manifest": { |  | ||||||
|         "name": "Disk Usage", |  | ||||||
|         "author": "ITDominator", |  | ||||||
|         "version": "0.0.1", |  | ||||||
|         "support": "", |  | ||||||
|         "requests": { |  | ||||||
|             "ui_target": "context_menu_plugins", |  | ||||||
|             "pass_fm_events": "true" |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,94 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os |  | ||||||
| import subprocess |  | ||||||
| import time |  | ||||||
| import inspect |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from plugins.plugin_base import PluginBase |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Plugin(PluginBase): |  | ||||||
|     def __init__(self): |  | ||||||
|         super().__init__() |  | ||||||
|  |  | ||||||
|         self.name        = "Disk Usage"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus |  | ||||||
|                                          #       where self.name should not be needed for message comms |  | ||||||
|  |  | ||||||
|         self.path        = os.path.dirname(os.path.realpath(__file__)) |  | ||||||
|         self._GLADE_FILE = f"{self.path}/du_usage.glade" |  | ||||||
|         self._du_dialog  = None |  | ||||||
|         self._du_store   = None |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def run(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) |  | ||||||
|  |  | ||||||
|         self._du_dialog = self._builder.get_object("du_dialog") |  | ||||||
|         self._du_store  = self._builder.get_object("du_store") |  | ||||||
|         self._current_dir_lbl  = self._builder.get_object("current_dir_lbl") |  | ||||||
|  |  | ||||||
|         self._event_system.subscribe("show_du_menu", self._show_du_menu) |  | ||||||
|  |  | ||||||
|     def generate_reference_ui_element(self): |  | ||||||
|         item = Gtk.ImageMenuItem(self.name) |  | ||||||
|         item.set_image( Gtk.Image(stock=Gtk.STOCK_HARDDISK) ) |  | ||||||
|         item.connect("activate", self._show_du_menu) |  | ||||||
|         item.set_always_show_image(True) |  | ||||||
|         return item |  | ||||||
|  |  | ||||||
|     def _get_state(self, widget=None, eve=None): |  | ||||||
|         self._event_system.emit("get_current_state") |  | ||||||
|  |  | ||||||
|     def _set_current_dir_lbl(self, widget=None, eve=None): |  | ||||||
|         self._current_dir_lbl.set_label(f"Current Directory:\n{self._fm_state.tab.get_current_directory()}") |  | ||||||
|  |  | ||||||
|     def _show_du_menu(self, widget=None, eve=None): |  | ||||||
|         self._fm_state = None |  | ||||||
|         self._get_state() |  | ||||||
|         self._set_current_dir_lbl() |  | ||||||
|         self.load_du_data() |  | ||||||
|         self._du_dialog.run() |  | ||||||
|  |  | ||||||
|     def load_du_data(self): |  | ||||||
|         self._du_store.clear() |  | ||||||
|  |  | ||||||
|         path     = self._fm_state.tab.get_current_directory() |  | ||||||
|         # NOTE: -h = human readable, -d = depth asigned to 1 |  | ||||||
|         command  = ["du", "-h", "-d", "1", path] |  | ||||||
|         proc     = subprocess.Popen(command, stdout=subprocess.PIPE) |  | ||||||
|         raw_data = proc.communicate()[0] |  | ||||||
|         data     = raw_data.decode("utf-8").strip()  # NOTE: Will return data AFTER completion (if any) |  | ||||||
|         parts    = data.split("\n") |  | ||||||
|  |  | ||||||
|         # NOTE: Last entry is curret dir. Move to top of list and pop off... |  | ||||||
|         size, file = parts[-1].split("\t") |  | ||||||
|         self._du_store.append([size, file.split("/")[-1]]) |  | ||||||
|         parts.pop() |  | ||||||
|  |  | ||||||
|         for part in parts: |  | ||||||
|             size, file = part.split("\t") |  | ||||||
|             self._du_store.append([size, file.split("/")[-1]]) |  | ||||||
|  |  | ||||||
|     def _hide_du_menu(self, widget=None, eve=None): |  | ||||||
|         self._du_dialog.hide() |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Module |  | ||||||
| """ |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Package |  | ||||||
| """ |  | ||||||
| @@ -1,158 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <!-- Generated with glade 3.40.0 --> |  | ||||||
| <interface> |  | ||||||
|   <requires lib="gtk+" version="3.24"/> |  | ||||||
|   <object class="GtkListStore" id="favorites_store"> |  | ||||||
|     <columns> |  | ||||||
|       <!-- column-name Favorites --> |  | ||||||
|       <column type="gchararray"/> |  | ||||||
|       <!-- column-name Path --> |  | ||||||
|       <column type="gchararray"/> |  | ||||||
|     </columns> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkDialog" id="favorites_dialog"> |  | ||||||
|     <property name="width-request">420</property> |  | ||||||
|     <property name="height-request">450</property> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="modal">True</property> |  | ||||||
|     <property name="window-position">center</property> |  | ||||||
|     <property name="destroy-with-parent">True</property> |  | ||||||
|     <property name="type-hint">dialog</property> |  | ||||||
|     <property name="skip-taskbar-hint">True</property> |  | ||||||
|     <property name="skip-pager-hint">True</property> |  | ||||||
|     <property name="decorated">False</property> |  | ||||||
|     <property name="deletable">False</property> |  | ||||||
|     <property name="gravity">center</property> |  | ||||||
|     <child internal-child="vbox"> |  | ||||||
|       <object class="GtkBox"> |  | ||||||
|         <property name="can-focus">False</property> |  | ||||||
|         <property name="orientation">vertical</property> |  | ||||||
|         <property name="spacing">2</property> |  | ||||||
|         <child internal-child="action_area"> |  | ||||||
|           <object class="GtkButtonBox"> |  | ||||||
|             <property name="can-focus">False</property> |  | ||||||
|             <property name="layout-style">end</property> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkButton"> |  | ||||||
|                 <property name="label">gtk-delete</property> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="receives-default">True</property> |  | ||||||
|                 <property name="use-stock">True</property> |  | ||||||
|                 <property name="always-show-image">True</property> |  | ||||||
|                 <signal name="released" handler="_remove_from_favorite" 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"> |  | ||||||
|                 <property name="label">gtk-add</property> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="receives-default">True</property> |  | ||||||
|                 <property name="use-stock">True</property> |  | ||||||
|                 <property name="always-show-image">True</property> |  | ||||||
|                 <signal name="released" handler="_add_to_favorite" 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">gtk-close</property> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="receives-default">True</property> |  | ||||||
|                 <property name="use-stock">True</property> |  | ||||||
|                 <signal name="released" handler="_hide_favorites_menu" swapped="no"/> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">True</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">2</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|           <packing> |  | ||||||
|             <property name="expand">False</property> |  | ||||||
|             <property name="fill">False</property> |  | ||||||
|             <property name="position">0</property> |  | ||||||
|           </packing> |  | ||||||
|         </child> |  | ||||||
|         <child> |  | ||||||
|           <object class="GtkBox"> |  | ||||||
|             <property name="visible">True</property> |  | ||||||
|             <property name="can-focus">False</property> |  | ||||||
|             <property name="orientation">vertical</property> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkLabel" id="current_dir_lbl"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="margin-start">5</property> |  | ||||||
|                 <property name="margin-end">5</property> |  | ||||||
|                 <property name="margin-top">5</property> |  | ||||||
|                 <property name="margin-bottom">5</property> |  | ||||||
|                 <property name="label" translatable="yes">Current Directory:</property> |  | ||||||
|                 <property name="justify">center</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">False</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">0</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkScrolledWindow"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="shadow-type">in</property> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkTreeView"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">True</property> |  | ||||||
|                     <property name="model">favorites_store</property> |  | ||||||
|                     <property name="headers-clickable">False</property> |  | ||||||
|                     <signal name="button-release-event" handler="_set_selected_path" swapped="no"/> |  | ||||||
|                     <child internal-child="selection"> |  | ||||||
|                       <object class="GtkTreeSelection"> |  | ||||||
|                         <signal name="changed" handler="_set_selected" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkTreeViewColumn"> |  | ||||||
|                         <property name="title" translatable="yes">Favorites</property> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkCellRendererText"/> |  | ||||||
|                           <attributes> |  | ||||||
|                             <attribute name="text">0</attribute> |  | ||||||
|                           </attributes> |  | ||||||
|                         </child> |  | ||||||
|                       </object> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                 </child> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">True</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">1</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|           <packing> |  | ||||||
|             <property name="expand">True</property> |  | ||||||
|             <property name="fill">True</property> |  | ||||||
|             <property name="position">1</property> |  | ||||||
|           </packing> |  | ||||||
|         </child> |  | ||||||
|       </object> |  | ||||||
|     </child> |  | ||||||
|   </object> |  | ||||||
| </interface> |  | ||||||
| @@ -1,14 +0,0 @@ | |||||||
| { |  | ||||||
|     "manifest": { |  | ||||||
|         "name": "Favorites", |  | ||||||
|         "author": "ITDominator", |  | ||||||
|         "version": "0.0.1", |  | ||||||
|         "support": "", |  | ||||||
|         "requests": { |  | ||||||
|             "ui_target": "main_menu_bttn_box_bar", |  | ||||||
|             "pass_fm_events": "true", |  | ||||||
|             "pass_ui_objects": ["path_entry"], |  | ||||||
|             "bind_keys": ["Favorites||show_favorites_menu:<Control>f"] |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,117 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os |  | ||||||
| import inspect |  | ||||||
| import json |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from plugins.plugin_base import PluginBase |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Plugin(PluginBase): |  | ||||||
|     def __init__(self): |  | ||||||
|         super().__init__() |  | ||||||
|  |  | ||||||
|         self.name               = "Favorites"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus |  | ||||||
|                                                 #       where self.name should not be needed for message comms |  | ||||||
|         self.path               = os.path.dirname(os.path.realpath(__file__)) |  | ||||||
|         self._GLADE_FILE        = f"{self.path}/favorites.glade" |  | ||||||
|         self._FAVORITES_FILE    = f"{self.path}/favorites.json" |  | ||||||
|  |  | ||||||
|         self._favorites_dialog  = None |  | ||||||
|         self._favorites_store   = None |  | ||||||
|         self._favorites         = None |  | ||||||
|         self._selected          = None |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def run(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) |  | ||||||
|  |  | ||||||
|         self._favorites_dialog = self._builder.get_object("favorites_dialog") |  | ||||||
|         self._favorites_store  = self._builder.get_object("favorites_store") |  | ||||||
|         self._current_dir_lbl  = self._builder.get_object("current_dir_lbl") |  | ||||||
|  |  | ||||||
|         if os.path.exists(self._FAVORITES_FILE): |  | ||||||
|             with open(self._FAVORITES_FILE) as f: |  | ||||||
|                 self._favorites = json.load(f) |  | ||||||
|                 for favorite in self._favorites: |  | ||||||
|                     display, path = favorite |  | ||||||
|                     self._favorites_store.append([display, path]) |  | ||||||
|         else: |  | ||||||
|             with open(self._FAVORITES_FILE, 'a') as f: |  | ||||||
|                 f.write('[]') |  | ||||||
|  |  | ||||||
|         self._event_system.subscribe("show_favorites_menu", self._show_favorites_menu) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def generate_reference_ui_element(self): |  | ||||||
|         button = Gtk.Button(label=self.name) |  | ||||||
|         button.connect("button-release-event", self._show_favorites_menu) |  | ||||||
|         return button |  | ||||||
|  |  | ||||||
|     def _get_state(self, widget=None, eve=None): |  | ||||||
|         self._event_system.emit("get_current_state") |  | ||||||
|  |  | ||||||
|     def _set_current_dir_lbl(self, widget=None, eve=None): |  | ||||||
|         self._current_dir_lbl.set_label(f"Current Directory:\n{self._fm_state.tab.get_current_directory()}") |  | ||||||
|  |  | ||||||
|     def _add_to_favorite(self, state): |  | ||||||
|         path    = self._fm_state.tab.get_current_directory() |  | ||||||
|         parts   = path.split("/") |  | ||||||
|         display = '/'.join(parts[-3:]) if len(parts) > 3 else path |  | ||||||
|  |  | ||||||
|         self._favorites_store.append([display, path]) |  | ||||||
|         self._favorites.append([display, path]) |  | ||||||
|         self._save_favorites() |  | ||||||
|  |  | ||||||
|     def _remove_from_favorite(self, state): |  | ||||||
|         path = self._favorites_store.get_value(self._selected, 1) |  | ||||||
|         self._favorites_store.remove(self._selected) |  | ||||||
|  |  | ||||||
|         for i, f in enumerate(self._favorites): |  | ||||||
|             if f[1] == path: |  | ||||||
|                 self._favorites.remove( self._favorites[i] ) |  | ||||||
|  |  | ||||||
|         self._save_favorites() |  | ||||||
|  |  | ||||||
|     def _save_favorites(self): |  | ||||||
|         with open(self._FAVORITES_FILE, 'w') as outfile: |  | ||||||
|             json.dump(self._favorites, outfile, separators=(',', ':'), indent=4) |  | ||||||
|  |  | ||||||
|     def _set_selected_path(self, widget=None, eve=None): |  | ||||||
|         path = self._favorites_store.get_value(self._selected, 1) |  | ||||||
|         self._ui_objects[0].set_text(path) |  | ||||||
|         self._set_current_dir_lbl() |  | ||||||
|  |  | ||||||
|     def _show_favorites_menu(self, widget=None, eve=None): |  | ||||||
|         self._fm_state = None |  | ||||||
|         self._get_state() |  | ||||||
|         self._set_current_dir_lbl() |  | ||||||
|         self._favorites_dialog.run() |  | ||||||
|  |  | ||||||
|     def _hide_favorites_menu(self, widget=None, eve=None): |  | ||||||
|         self._favorites_dialog.hide() |  | ||||||
|  |  | ||||||
|     def _set_selected(self, user_data): |  | ||||||
|         selected = user_data.get_selected()[1] |  | ||||||
|         if selected and not self._selected == selected: |  | ||||||
|             self._selected = selected |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Module |  | ||||||
| """ |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Package |  | ||||||
| """ |  | ||||||
| @@ -1,685 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <!-- Generated with glade 3.38.2 --> |  | ||||||
| <interface> |  | ||||||
|   <requires lib="gtk+" version="3.16"/> |  | ||||||
|   <object class="GtkDialog" id="file_properties_dialog"> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="border-width">6</property> |  | ||||||
|     <property name="title" translatable="yes">File Properties</property> |  | ||||||
|     <property name="modal">True</property> |  | ||||||
|     <property name="window-position">center-on-parent</property> |  | ||||||
|     <property name="default-width">420</property> |  | ||||||
|     <property name="destroy-with-parent">True</property> |  | ||||||
|     <property name="type-hint">dialog</property> |  | ||||||
|     <property name="skip-taskbar-hint">True</property> |  | ||||||
|     <property name="skip-pager-hint">True</property> |  | ||||||
|     <property name="gravity">center</property> |  | ||||||
|     <signal name="response" handler="on_filePropertiesDlg_response" swapped="no"/> |  | ||||||
|     <child internal-child="vbox"> |  | ||||||
|       <object class="GtkBox" id="dialog_vbox"> |  | ||||||
|         <property name="visible">True</property> |  | ||||||
|         <property name="can-focus">False</property> |  | ||||||
|         <property name="spacing">12</property> |  | ||||||
|         <child internal-child="action_area"> |  | ||||||
|           <object class="GtkButtonBox" id="dialog_action_area"> |  | ||||||
|             <property name="visible">True</property> |  | ||||||
|             <property name="can-focus">False</property> |  | ||||||
|             <property name="layout-style">end</property> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkButton" id="cancel_button"> |  | ||||||
|                 <property name="label">gtk-cancel</property> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="can-default">True</property> |  | ||||||
|                 <property name="receives-default">False</property> |  | ||||||
|                 <property name="use-stock">True</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">True</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">0</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkButton" id="ok_button"> |  | ||||||
|                 <property name="label">gtk-ok</property> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="can-default">True</property> |  | ||||||
|                 <property name="receives-default">False</property> |  | ||||||
|                 <property name="use-stock">True</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">True</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">1</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|           <packing> |  | ||||||
|             <property name="expand">False</property> |  | ||||||
|             <property name="fill">False</property> |  | ||||||
|             <property name="pack-type">end</property> |  | ||||||
|             <property name="position">0</property> |  | ||||||
|           </packing> |  | ||||||
|         </child> |  | ||||||
|         <child> |  | ||||||
|           <object class="GtkNotebook" id="notebook"> |  | ||||||
|             <property name="visible">True</property> |  | ||||||
|             <property name="can-focus">True</property> |  | ||||||
|             <property name="border-width">6</property> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkAlignment" id="alignment2"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="top-padding">6</property> |  | ||||||
|                 <property name="bottom-padding">6</property> |  | ||||||
|                 <property name="left-padding">12</property> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkTable" id="general_table"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="border-width">4</property> |  | ||||||
|                     <property name="n-rows">7</property> |  | ||||||
|                     <property name="n-columns">2</property> |  | ||||||
|                     <property name="column-spacing">12</property> |  | ||||||
|                     <property name="row-spacing">6</property> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkLabel" id="label_filename"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">False</property> |  | ||||||
|                         <property name="label" translatable="yes"><b>File _Name:</b></property> |  | ||||||
|                         <property name="use-markup">True</property> |  | ||||||
|                         <property name="use-underline">True</property> |  | ||||||
|                         <property name="mnemonic-widget">file_name</property> |  | ||||||
|                         <property name="xalign">0</property> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="x-options">GTK_FILL</property> |  | ||||||
|                         <property name="y-options"/> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkEntry" id="file_name"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="left-attach">1</property> |  | ||||||
|                         <property name="right-attach">2</property> |  | ||||||
|                         <property name="y-options"/> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkLabel" id="label20"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">False</property> |  | ||||||
|                         <property name="label" translatable="yes"><b>_Location:</b></property> |  | ||||||
|                         <property name="use-markup">True</property> |  | ||||||
|                         <property name="use-underline">True</property> |  | ||||||
|                         <property name="mnemonic-widget">file_location</property> |  | ||||||
|                         <property name="xalign">0</property> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="top-attach">1</property> |  | ||||||
|                         <property name="bottom-attach">2</property> |  | ||||||
|                         <property name="x-options">GTK_FILL</property> |  | ||||||
|                         <property name="y-options"/> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkEntry" id="file_location"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="left-attach">1</property> |  | ||||||
|                         <property name="right-attach">2</property> |  | ||||||
|                         <property name="top-attach">1</property> |  | ||||||
|                         <property name="bottom-attach">2</property> |  | ||||||
|                         <property name="x-options">GTK_FILL</property> |  | ||||||
|                         <property name="y-options"/> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkLabel" id="label_target"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">False</property> |  | ||||||
|                         <property name="label" translatable="yes"><b>Link _Target:</b></property> |  | ||||||
|                         <property name="use-markup">True</property> |  | ||||||
|                         <property name="use-underline">True</property> |  | ||||||
|                         <property name="mnemonic-widget">file_target</property> |  | ||||||
|                         <property name="xalign">0</property> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="top-attach">2</property> |  | ||||||
|                         <property name="bottom-attach">3</property> |  | ||||||
|                         <property name="x-options">GTK_FILL</property> |  | ||||||
|                         <property name="y-options"/> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkEntry" id="file_target"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="left-attach">1</property> |  | ||||||
|                         <property name="right-attach">2</property> |  | ||||||
|                         <property name="top-attach">2</property> |  | ||||||
|                         <property name="bottom-attach">3</property> |  | ||||||
|                         <property name="x-options">GTK_FILL</property> |  | ||||||
|                         <property name="y-options"/> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkLabel" id="label4"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">False</property> |  | ||||||
|                         <property name="label" translatable="yes"><b>Type:</b></property> |  | ||||||
|                         <property name="use-markup">True</property> |  | ||||||
|                         <property name="use-underline">True</property> |  | ||||||
|                         <property name="xalign">0</property> |  | ||||||
|                         <property name="yalign">0</property> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="top-attach">3</property> |  | ||||||
|                         <property name="bottom-attach">4</property> |  | ||||||
|                         <property name="x-options">GTK_FILL</property> |  | ||||||
|                         <property name="y-options">GTK_FILL</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkLabel" id="mime_type"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="selectable">True</property> |  | ||||||
|                         <property name="ellipsize">end</property> |  | ||||||
|                         <property name="xalign">0</property> |  | ||||||
|                         <property name="yalign">0</property> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="left-attach">1</property> |  | ||||||
|                         <property name="right-attach">2</property> |  | ||||||
|                         <property name="top-attach">3</property> |  | ||||||
|                         <property name="bottom-attach">4</property> |  | ||||||
|                         <property name="x-options">GTK_FILL</property> |  | ||||||
|                         <property name="y-options"/> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkLabel" id="label5"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">False</property> |  | ||||||
|                         <property name="label" translatable="yes"><b>Size:</b></property> |  | ||||||
|                         <property name="use-markup">True</property> |  | ||||||
|                         <property name="use-underline">True</property> |  | ||||||
|                         <property name="xalign">0</property> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="top-attach">4</property> |  | ||||||
|                         <property name="bottom-attach">5</property> |  | ||||||
|                         <property name="x-options">GTK_FILL</property> |  | ||||||
|                         <property name="y-options"/> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkLabel" id="file_size"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="selectable">True</property> |  | ||||||
|                         <property name="xalign">0</property> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="left-attach">1</property> |  | ||||||
|                         <property name="right-attach">2</property> |  | ||||||
|                         <property name="top-attach">4</property> |  | ||||||
|                         <property name="bottom-attach">5</property> |  | ||||||
|                         <property name="x-options">GTK_FILL</property> |  | ||||||
|                         <property name="y-options"/> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkLabel" id="label7"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">False</property> |  | ||||||
|                         <property name="label" translatable="yes"><b>_Modified:</b></property> |  | ||||||
|                         <property name="use-markup">True</property> |  | ||||||
|                         <property name="use-underline">True</property> |  | ||||||
|                         <property name="xalign">0</property> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="top-attach">5</property> |  | ||||||
|                         <property name="bottom-attach">6</property> |  | ||||||
|                         <property name="x-options">GTK_FILL</property> |  | ||||||
|                         <property name="y-options"/> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkLabel" id="label13"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">False</property> |  | ||||||
|                         <property name="label" translatable="yes"><b>_Accessed:</b></property> |  | ||||||
|                         <property name="use-markup">True</property> |  | ||||||
|                         <property name="use-underline">True</property> |  | ||||||
|                         <property name="xalign">0</property> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="top-attach">6</property> |  | ||||||
|                         <property name="bottom-attach">7</property> |  | ||||||
|                         <property name="x-options">GTK_FILL</property> |  | ||||||
|                         <property name="y-options"/> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkEntry" id="mtime"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="left-attach">1</property> |  | ||||||
|                         <property name="right-attach">2</property> |  | ||||||
|                         <property name="top-attach">5</property> |  | ||||||
|                         <property name="bottom-attach">6</property> |  | ||||||
|                         <property name="x-options">GTK_FILL</property> |  | ||||||
|                         <property name="y-options"/> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkEntry" id="atime"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="left-attach">1</property> |  | ||||||
|                         <property name="right-attach">2</property> |  | ||||||
|                         <property name="top-attach">6</property> |  | ||||||
|                         <property name="bottom-attach">7</property> |  | ||||||
|                         <property name="x-options">GTK_FILL</property> |  | ||||||
|                         <property name="y-options"/> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                 </child> |  | ||||||
|               </object> |  | ||||||
|             </child> |  | ||||||
|             <child type="tab"> |  | ||||||
|               <object class="GtkLabel" id="label1"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="label" translatable="yes">_Info</property> |  | ||||||
|                 <property name="use-underline">True</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="tab-fill">False</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkAlignment" id="alignment1"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="top-padding">6</property> |  | ||||||
|                 <property name="bottom-padding">6</property> |  | ||||||
|                 <property name="left-padding">12</property> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkVBox" id="vbox1"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="spacing">6</property> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkTable" id="table3"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">False</property> |  | ||||||
|                         <property name="border-width">2</property> |  | ||||||
|                         <property name="n-rows">2</property> |  | ||||||
|                         <property name="n-columns">2</property> |  | ||||||
|                         <property name="column-spacing">12</property> |  | ||||||
|                         <property name="row-spacing">6</property> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkLabel" id="owner_label"> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">False</property> |  | ||||||
|                             <property name="label" translatable="yes"><b>_Owner:</b></property> |  | ||||||
|                             <property name="use-markup">True</property> |  | ||||||
|                             <property name="use-underline">True</property> |  | ||||||
|                             <property name="mnemonic-widget">file_owner</property> |  | ||||||
|                             <property name="xalign">0</property> |  | ||||||
|                           </object> |  | ||||||
|                           <packing> |  | ||||||
|                             <property name="x-options">GTK_FILL</property> |  | ||||||
|                             <property name="y-options"/> |  | ||||||
|                           </packing> |  | ||||||
|                         </child> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkLabel" id="group_label"> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">False</property> |  | ||||||
|                             <property name="label" translatable="yes"><b>_Group:</b></property> |  | ||||||
|                             <property name="use-markup">True</property> |  | ||||||
|                             <property name="use-underline">True</property> |  | ||||||
|                             <property name="mnemonic-widget">file_group</property> |  | ||||||
|                             <property name="xalign">0</property> |  | ||||||
|                           </object> |  | ||||||
|                           <packing> |  | ||||||
|                             <property name="top-attach">1</property> |  | ||||||
|                             <property name="bottom-attach">2</property> |  | ||||||
|                             <property name="x-options">GTK_FILL</property> |  | ||||||
|                             <property name="y-options"/> |  | ||||||
|                           </packing> |  | ||||||
|                         </child> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkEntry" id="file_owner"> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">True</property> |  | ||||||
|                           </object> |  | ||||||
|                           <packing> |  | ||||||
|                             <property name="left-attach">1</property> |  | ||||||
|                             <property name="right-attach">2</property> |  | ||||||
|                             <property name="y-options"/> |  | ||||||
|                           </packing> |  | ||||||
|                         </child> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkEntry" id="file_group"> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">True</property> |  | ||||||
|                           </object> |  | ||||||
|                           <packing> |  | ||||||
|                             <property name="left-attach">1</property> |  | ||||||
|                             <property name="right-attach">2</property> |  | ||||||
|                             <property name="top-attach">1</property> |  | ||||||
|                             <property name="bottom-attach">2</property> |  | ||||||
|                             <property name="y-options"/> |  | ||||||
|                           </packing> |  | ||||||
|                         </child> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">False</property> |  | ||||||
|                         <property name="position">0</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkHSeparator" id="hseparator1"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">False</property> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">False</property> |  | ||||||
|                         <property name="position">1</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkTable" id="table2"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">False</property> |  | ||||||
|                         <property name="border-width">4</property> |  | ||||||
|                         <property name="n-rows">3</property> |  | ||||||
|                         <property name="n-columns">5</property> |  | ||||||
|                         <property name="column-spacing">12</property> |  | ||||||
|                         <property name="row-spacing">6</property> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkLabel" id="label17"> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">False</property> |  | ||||||
|                             <property name="label" translatable="yes"><b>Owner:</b></property> |  | ||||||
|                             <property name="use-markup">True</property> |  | ||||||
|                             <property name="use-underline">True</property> |  | ||||||
|                             <property name="xalign">0</property> |  | ||||||
|                           </object> |  | ||||||
|                           <packing> |  | ||||||
|                             <property name="x-options">GTK_FILL</property> |  | ||||||
|                             <property name="y-options"/> |  | ||||||
|                           </packing> |  | ||||||
|                         </child> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkLabel" id="label18"> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">False</property> |  | ||||||
|                             <property name="label" translatable="yes"><b>Group:</b></property> |  | ||||||
|                             <property name="use-markup">True</property> |  | ||||||
|                             <property name="use-underline">True</property> |  | ||||||
|                             <property name="xalign">0</property> |  | ||||||
|                           </object> |  | ||||||
|                           <packing> |  | ||||||
|                             <property name="top-attach">1</property> |  | ||||||
|                             <property name="bottom-attach">2</property> |  | ||||||
|                             <property name="x-options">GTK_FILL</property> |  | ||||||
|                             <property name="y-options"/> |  | ||||||
|                           </packing> |  | ||||||
|                         </child> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkLabel" id="label19"> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">False</property> |  | ||||||
|                             <property name="label" translatable="yes"><b>Other:</b></property> |  | ||||||
|                             <property name="use-markup">True</property> |  | ||||||
|                             <property name="use-underline">True</property> |  | ||||||
|                             <property name="xalign">0</property> |  | ||||||
|                           </object> |  | ||||||
|                           <packing> |  | ||||||
|                             <property name="top-attach">2</property> |  | ||||||
|                             <property name="bottom-attach">3</property> |  | ||||||
|                             <property name="x-options">GTK_FILL</property> |  | ||||||
|                             <property name="y-options"/> |  | ||||||
|                           </packing> |  | ||||||
|                         </child> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkCheckButton" id="owner_r"> |  | ||||||
|                             <property name="label" translatable="yes">Read</property> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">True</property> |  | ||||||
|                             <property name="receives-default">False</property> |  | ||||||
|                             <property name="border-width">2</property> |  | ||||||
|                             <property name="use-underline">True</property> |  | ||||||
|                             <property name="draw-indicator">True</property> |  | ||||||
|                           </object> |  | ||||||
|                           <packing> |  | ||||||
|                             <property name="left-attach">1</property> |  | ||||||
|                             <property name="right-attach">2</property> |  | ||||||
|                             <property name="x-options">GTK_FILL</property> |  | ||||||
|                             <property name="y-options"/> |  | ||||||
|                           </packing> |  | ||||||
|                         </child> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkCheckButton" id="group_r"> |  | ||||||
|                             <property name="label" translatable="yes">Read</property> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">True</property> |  | ||||||
|                             <property name="receives-default">False</property> |  | ||||||
|                             <property name="border-width">2</property> |  | ||||||
|                             <property name="use-underline">True</property> |  | ||||||
|                             <property name="draw-indicator">True</property> |  | ||||||
|                           </object> |  | ||||||
|                           <packing> |  | ||||||
|                             <property name="left-attach">1</property> |  | ||||||
|                             <property name="right-attach">2</property> |  | ||||||
|                             <property name="top-attach">1</property> |  | ||||||
|                             <property name="bottom-attach">2</property> |  | ||||||
|                             <property name="x-options">GTK_FILL</property> |  | ||||||
|                             <property name="y-options"/> |  | ||||||
|                           </packing> |  | ||||||
|                         </child> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkCheckButton" id="others_r"> |  | ||||||
|                             <property name="label" translatable="yes">Read</property> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">True</property> |  | ||||||
|                             <property name="receives-default">False</property> |  | ||||||
|                             <property name="border-width">2</property> |  | ||||||
|                             <property name="use-underline">True</property> |  | ||||||
|                             <property name="draw-indicator">True</property> |  | ||||||
|                           </object> |  | ||||||
|                           <packing> |  | ||||||
|                             <property name="left-attach">1</property> |  | ||||||
|                             <property name="right-attach">2</property> |  | ||||||
|                             <property name="top-attach">2</property> |  | ||||||
|                             <property name="bottom-attach">3</property> |  | ||||||
|                             <property name="x-options">GTK_FILL</property> |  | ||||||
|                             <property name="y-options"/> |  | ||||||
|                           </packing> |  | ||||||
|                         </child> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkCheckButton" id="owner_w"> |  | ||||||
|                             <property name="label" translatable="yes">Write</property> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">True</property> |  | ||||||
|                             <property name="receives-default">False</property> |  | ||||||
|                             <property name="border-width">2</property> |  | ||||||
|                             <property name="use-underline">True</property> |  | ||||||
|                             <property name="draw-indicator">True</property> |  | ||||||
|                           </object> |  | ||||||
|                           <packing> |  | ||||||
|                             <property name="left-attach">2</property> |  | ||||||
|                             <property name="right-attach">3</property> |  | ||||||
|                             <property name="x-options">GTK_FILL</property> |  | ||||||
|                             <property name="y-options"/> |  | ||||||
|                           </packing> |  | ||||||
|                         </child> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkCheckButton" id="group_w"> |  | ||||||
|                             <property name="label" translatable="yes">Write</property> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">True</property> |  | ||||||
|                             <property name="receives-default">False</property> |  | ||||||
|                             <property name="border-width">2</property> |  | ||||||
|                             <property name="use-underline">True</property> |  | ||||||
|                             <property name="draw-indicator">True</property> |  | ||||||
|                           </object> |  | ||||||
|                           <packing> |  | ||||||
|                             <property name="left-attach">2</property> |  | ||||||
|                             <property name="right-attach">3</property> |  | ||||||
|                             <property name="top-attach">1</property> |  | ||||||
|                             <property name="bottom-attach">2</property> |  | ||||||
|                             <property name="x-options">GTK_FILL</property> |  | ||||||
|                             <property name="y-options"/> |  | ||||||
|                           </packing> |  | ||||||
|                         </child> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkCheckButton" id="others_w"> |  | ||||||
|                             <property name="label" translatable="yes">Write</property> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">True</property> |  | ||||||
|                             <property name="receives-default">False</property> |  | ||||||
|                             <property name="border-width">2</property> |  | ||||||
|                             <property name="use-underline">True</property> |  | ||||||
|                             <property name="draw-indicator">True</property> |  | ||||||
|                           </object> |  | ||||||
|                           <packing> |  | ||||||
|                             <property name="left-attach">2</property> |  | ||||||
|                             <property name="right-attach">3</property> |  | ||||||
|                             <property name="top-attach">2</property> |  | ||||||
|                             <property name="bottom-attach">3</property> |  | ||||||
|                             <property name="x-options">GTK_FILL</property> |  | ||||||
|                             <property name="y-options"/> |  | ||||||
|                           </packing> |  | ||||||
|                         </child> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkCheckButton" id="owner_x"> |  | ||||||
|                             <property name="label" translatable="yes">Execute</property> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">True</property> |  | ||||||
|                             <property name="receives-default">False</property> |  | ||||||
|                             <property name="border-width">2</property> |  | ||||||
|                             <property name="use-underline">True</property> |  | ||||||
|                             <property name="draw-indicator">True</property> |  | ||||||
|                           </object> |  | ||||||
|                           <packing> |  | ||||||
|                             <property name="left-attach">3</property> |  | ||||||
|                             <property name="right-attach">4</property> |  | ||||||
|                             <property name="x-options">GTK_FILL</property> |  | ||||||
|                             <property name="y-options"/> |  | ||||||
|                           </packing> |  | ||||||
|                         </child> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkCheckButton" id="group_x"> |  | ||||||
|                             <property name="label" translatable="yes">Execute</property> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">True</property> |  | ||||||
|                             <property name="receives-default">False</property> |  | ||||||
|                             <property name="border-width">2</property> |  | ||||||
|                             <property name="use-underline">True</property> |  | ||||||
|                             <property name="draw-indicator">True</property> |  | ||||||
|                           </object> |  | ||||||
|                           <packing> |  | ||||||
|                             <property name="left-attach">3</property> |  | ||||||
|                             <property name="right-attach">4</property> |  | ||||||
|                             <property name="top-attach">1</property> |  | ||||||
|                             <property name="bottom-attach">2</property> |  | ||||||
|                             <property name="x-options">GTK_FILL</property> |  | ||||||
|                             <property name="y-options"/> |  | ||||||
|                           </packing> |  | ||||||
|                         </child> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkCheckButton" id="others_x"> |  | ||||||
|                             <property name="label" translatable="yes">Execute</property> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">True</property> |  | ||||||
|                             <property name="receives-default">False</property> |  | ||||||
|                             <property name="border-width">2</property> |  | ||||||
|                             <property name="use-underline">True</property> |  | ||||||
|                             <property name="draw-indicator">True</property> |  | ||||||
|                           </object> |  | ||||||
|                           <packing> |  | ||||||
|                             <property name="left-attach">3</property> |  | ||||||
|                             <property name="right-attach">4</property> |  | ||||||
|                             <property name="top-attach">2</property> |  | ||||||
|                             <property name="bottom-attach">3</property> |  | ||||||
|                             <property name="x-options">GTK_FILL</property> |  | ||||||
|                             <property name="y-options"/> |  | ||||||
|                           </packing> |  | ||||||
|                         </child> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkVSeparator" id="vseparator1"> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">False</property> |  | ||||||
|                           </object> |  | ||||||
|                           <packing> |  | ||||||
|                             <property name="left-attach">4</property> |  | ||||||
|                             <property name="right-attach">5</property> |  | ||||||
|                             <property name="bottom-attach">3</property> |  | ||||||
|                             <property name="x-options">GTK_FILL</property> |  | ||||||
|                             <property name="y-options">GTK_FILL</property> |  | ||||||
|                           </packing> |  | ||||||
|                         </child> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">2</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                 </child> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="position">1</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child type="tab"> |  | ||||||
|               <object class="GtkLabel" id="label2"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="label" translatable="yes">_Permissions</property> |  | ||||||
|                 <property name="use-underline">True</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="position">1</property> |  | ||||||
|                 <property name="tab-fill">False</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|           <packing> |  | ||||||
|             <property name="expand">False</property> |  | ||||||
|             <property name="fill">True</property> |  | ||||||
|             <property name="position">2</property> |  | ||||||
|           </packing> |  | ||||||
|         </child> |  | ||||||
|       </object> |  | ||||||
|     </child> |  | ||||||
|     <action-widgets> |  | ||||||
|       <action-widget response="-6">cancel_button</action-widget> |  | ||||||
|       <action-widget response="-5">ok_button</action-widget> |  | ||||||
|     </action-widgets> |  | ||||||
|   </object> |  | ||||||
| </interface> |  | ||||||
| @@ -1,12 +0,0 @@ | |||||||
| { |  | ||||||
|     "manifest": { |  | ||||||
|         "name": "Properties", |  | ||||||
|         "author": "ITDominator", |  | ||||||
|         "version": "0.0.1", |  | ||||||
|         "support": "", |  | ||||||
|         "requests": { |  | ||||||
|             "ui_target": "context_menu", |  | ||||||
|             "pass_fm_events": "true" |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,249 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os |  | ||||||
| import threading |  | ||||||
| import subprocess |  | ||||||
| import time |  | ||||||
| import pwd |  | ||||||
| import grp |  | ||||||
| from datetime import datetime |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk |  | ||||||
| from gi.repository import GLib |  | ||||||
| from gi.repository import Gio |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from plugins.plugin_base import PluginBase |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # 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 Properties: |  | ||||||
|     file_uri: str      = None |  | ||||||
|     file_name: str     = None |  | ||||||
|     file_location: str = None |  | ||||||
|     file_target: str   = None |  | ||||||
|     mime_type: str     = None |  | ||||||
|     file_size: str     = None |  | ||||||
|     mtime: int         = None |  | ||||||
|     atime: int         = None |  | ||||||
|     file_owner: str    = None |  | ||||||
|     file_group: str    = None |  | ||||||
|     chmod_stat: str    = None |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Plugin(PluginBase): |  | ||||||
|     def __init__(self): |  | ||||||
|         super().__init__() |  | ||||||
|  |  | ||||||
|         self.path               = os.path.dirname(os.path.realpath(__file__)) |  | ||||||
|         self._GLADE_FILE        = f"{self.path}/file_properties.glade" |  | ||||||
|         self.name               = "Properties"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus |  | ||||||
|                                                 #       where self.name should not be needed for message comms |  | ||||||
|  |  | ||||||
|         self._properties_dialog = None |  | ||||||
|         self._file_name         = None |  | ||||||
|         self._file_location     = None |  | ||||||
|         self._file_target       = None |  | ||||||
|         self._mime_type         = None |  | ||||||
|         self._file_size         = None |  | ||||||
|         self._mtime             = None |  | ||||||
|         self._atime             = None |  | ||||||
|         self._file_owner        = None |  | ||||||
|         self._file_group        = None |  | ||||||
|  |  | ||||||
|         self._chmod_map: {} = { |  | ||||||
|                                 "7": "rwx", |  | ||||||
|                                 "6": "rw", |  | ||||||
|                                 "5": "rx", |  | ||||||
|                                 "4": "r", |  | ||||||
|                                 "3": "wx", |  | ||||||
|                                 "2": "w", |  | ||||||
|                                 "1": "x", |  | ||||||
|                                 "0": "" |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         self._chmod_map_counter: {} = { |  | ||||||
|                                  "rwx": "7", |  | ||||||
|                                  "rw":  "6", |  | ||||||
|                                  "rx":  "5", |  | ||||||
|                                  "r":   "4", |  | ||||||
|                                  "wx":  "3", |  | ||||||
|                                  "w":   "2", |  | ||||||
|                                  "x":   "1", |  | ||||||
|                                  "":    "0" |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def run(self): |  | ||||||
|         self._builder           = Gtk.Builder() |  | ||||||
|         self._builder.add_from_file(self._GLADE_FILE) |  | ||||||
|  |  | ||||||
|         self._properties_dialog = self._builder.get_object("file_properties_dialog") |  | ||||||
|         self._file_name     = self._builder.get_object("file_name") |  | ||||||
|         self._file_location = self._builder.get_object("file_location") |  | ||||||
|         self._file_target   = self._builder.get_object("file_target") |  | ||||||
|         self._mime_type     = self._builder.get_object("mime_type") |  | ||||||
|         self._file_size     = self._builder.get_object("file_size") |  | ||||||
|         self._mtime         = self._builder.get_object("mtime") |  | ||||||
|         self._atime         = self._builder.get_object("atime") |  | ||||||
|         self._file_owner    = self._builder.get_object("file_owner") |  | ||||||
|         self._file_group    = self._builder.get_object("file_group") |  | ||||||
|  |  | ||||||
|     def generate_reference_ui_element(self): |  | ||||||
|         item = Gtk.ImageMenuItem(self.name) |  | ||||||
|         item.set_image( Gtk.Image(stock=Gtk.STOCK_PROPERTIES) ) |  | ||||||
|         item.connect("activate", self._show_properties_page) |  | ||||||
|         item.set_always_show_image(True) |  | ||||||
|         return item |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def _show_properties_page(self, widget=None, eve=None): |  | ||||||
|         event_system.emit("get_current_state") |  | ||||||
|  |  | ||||||
|         state               = self._fm_state |  | ||||||
|         self._event_message = None |  | ||||||
|  |  | ||||||
|         GLib.idle_add(self._process_changes, (state)) |  | ||||||
|  |  | ||||||
|     def _process_changes(self, state): |  | ||||||
|         if len(state.selected_files) == 1: |  | ||||||
|             uri  = state.selected_files[0] |  | ||||||
|             path = state.tab.get_current_directory() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|             properties = self._set_ui_data(uri, path) |  | ||||||
|             response   = self._properties_dialog.run() |  | ||||||
|             if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]: |  | ||||||
|                 self._properties_dialog.hide() |  | ||||||
|  |  | ||||||
|             self._update_file(properties) |  | ||||||
|             self._properties_dialog.hide() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _update_file(self, properties): |  | ||||||
|         chmod_stat = self._get_check_boxes() |  | ||||||
|  |  | ||||||
|         if chmod_stat is not properties.chmod_stat: |  | ||||||
|             try: |  | ||||||
|                 print("\nNew chmod flags...") |  | ||||||
|                 print(f"Old:  {''.join(properties.chmod_stat)}") |  | ||||||
|                 print(f"New:  {chmod_stat}") |  | ||||||
|  |  | ||||||
|                 command = ["chmod", f"{chmod_stat}", properties.file_uri] |  | ||||||
|                 with subprocess.Popen(command, stdout=subprocess.PIPE) as proc: |  | ||||||
|                     result = proc.stdout.read().decode("UTF-8").strip() |  | ||||||
|                     print(result) |  | ||||||
|             except Exception as e: |  | ||||||
|                 print(f"Couldn't chmod\nFile:  {properties.file_uri}") |  | ||||||
|                 print( repr(e) ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         owner = self._file_owner.get_text() |  | ||||||
|         group = self._file_group.get_text() |  | ||||||
|         if owner is not properties.file_owner or group is not properties.file_group: |  | ||||||
|             try: |  | ||||||
|                 print("\nNew owner/group flags...") |  | ||||||
|                 print(f"Old:\n\tOwner: {properties.file_owner}\n\tGroup: {properties.file_group}") |  | ||||||
|                 print(f"New:\n\tOwner: {owner}\n\tGroup: {group}") |  | ||||||
|  |  | ||||||
|                 uid = pwd.getpwnam(owner).pw_uid |  | ||||||
|                 gid = grp.getgrnam(group).gr_gid |  | ||||||
|                 os.chown(properties.file_uri, uid, gid) |  | ||||||
|             except Exception as e: |  | ||||||
|                 print(f"Couldn't chmod\nFile:  {properties.file_uri}") |  | ||||||
|                 print( repr(e) ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _set_ui_data(self, uri, path): |  | ||||||
|         properties = Properties() |  | ||||||
|         file_info  = Gio.File.new_for_path(uri).query_info(attributes="standard::*,owner::*,time::access,time::changed", |  | ||||||
|                                                             flags=Gio.FileQueryInfoFlags.NONE, |  | ||||||
|                                                             cancellable=None) |  | ||||||
|  |  | ||||||
|         is_symlink               = file_info.get_attribute_as_string("standard::is-symlink") |  | ||||||
|         properties.file_uri      = uri |  | ||||||
|         properties.file_target   = file_info.get_attribute_as_string("standard::symlink-target") if is_symlink else "" |  | ||||||
|         properties.file_name     = file_info.get_display_name() |  | ||||||
|         properties.file_location = path |  | ||||||
|         properties.mime_type     = file_info.get_content_type() |  | ||||||
|         properties.file_size     = self._sizeof_fmt(file_info.get_size()) |  | ||||||
|         properties.mtime         = datetime.fromtimestamp( int(file_info.get_attribute_as_string("time::changed")) ).strftime("%A, %B %d, %Y %I:%M:%S") |  | ||||||
|         properties.atime         = datetime.fromtimestamp( int(file_info.get_attribute_as_string("time::access")) ).strftime("%A, %B %d, %Y %I:%M:%S") |  | ||||||
|         properties.file_owner    = file_info.get_attribute_as_string("owner::user") |  | ||||||
|         properties.file_group    = file_info.get_attribute_as_string("owner::group") |  | ||||||
|  |  | ||||||
|         # NOTE: Read = 4,  Write = 2,  Exec = 1 |  | ||||||
|         command = ["stat", "-c", "%a", uri] |  | ||||||
|         with subprocess.Popen(command, stdout=subprocess.PIPE) as proc: |  | ||||||
|             properties.chmod_stat = list(proc.stdout.read().decode("UTF-8").strip()) |  | ||||||
|             owner  = self._chmod_map[f"{properties.chmod_stat[0]}"] |  | ||||||
|             group  = self._chmod_map[f"{properties.chmod_stat[1]}"] |  | ||||||
|             others = self._chmod_map[f"{properties.chmod_stat[2]}"] |  | ||||||
|  |  | ||||||
|             self._reset_check_boxes() |  | ||||||
|             self._set_check_boxes([["owner", owner], ["group", group], ["others", others]]) |  | ||||||
|  |  | ||||||
|         self._file_name.set_text(properties.file_name) |  | ||||||
|         self._file_location.set_text(properties.file_location) |  | ||||||
|         self._file_target.set_text(properties.file_target) |  | ||||||
|         self._mime_type.set_label(properties.mime_type) |  | ||||||
|         self._file_size.set_label(properties.file_size) |  | ||||||
|         self._mtime.set_text(properties.mtime) |  | ||||||
|         self._atime.set_text(properties.atime) |  | ||||||
|         self._file_owner.set_text(properties.file_owner) |  | ||||||
|         self._file_group.set_text(properties.file_group) |  | ||||||
|  |  | ||||||
|         return properties |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _get_check_boxes(self): |  | ||||||
|         perms = [[], [], []] |  | ||||||
|  |  | ||||||
|         for i, target in enumerate(["owner", "group", "others"]): |  | ||||||
|             for type in ["r", "w", "x"]: |  | ||||||
|                 is_active = self._builder.get_object(f"{target}_{type}").get_active() |  | ||||||
|                 if is_active: |  | ||||||
|                     perms[i].append(type) |  | ||||||
|  |  | ||||||
|         digits = [] |  | ||||||
|         for perm in perms: |  | ||||||
|             digits.append(self._chmod_map_counter[ ''.join(perm) ]) |  | ||||||
|  |  | ||||||
|         return ''.join(digits) |  | ||||||
|  |  | ||||||
|     def _set_check_boxes(self, targets): |  | ||||||
|         for name, target in targets: |  | ||||||
|             for type in list(target): |  | ||||||
|                 obj = f"{name}_{type}" |  | ||||||
|                 self._builder.get_object(obj).set_active(True) |  | ||||||
|  |  | ||||||
|     def _reset_check_boxes(self): |  | ||||||
|         for target in ["owner", "group", "others"]: |  | ||||||
|             for type in ["r", "w", "x"]: |  | ||||||
|                 self._builder.get_object(f"{target}_{type}").set_active(False) |  | ||||||
|  |  | ||||||
|     def _sizeof_fmt(self, num, suffix="B"): |  | ||||||
|         for unit in ["", "K", "M", "G", "T", "Pi", "Ei", "Zi"]: |  | ||||||
|             if abs(num) < 1024.0: |  | ||||||
|                 return f"{num:3.1f} {unit}{suffix}" |  | ||||||
|             num /= 1024.0 |  | ||||||
|         return f"{num:.1f} Yi{suffix}" |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Module |  | ||||||
| """ |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Package |  | ||||||
| """ |  | ||||||
| @@ -1,12 +0,0 @@ | |||||||
| { |  | ||||||
|     "manifest": { |  | ||||||
|         "name": "Movie/TV Info", |  | ||||||
|         "author": "ITDominator", |  | ||||||
|         "version": "0.0.1", |  | ||||||
|         "support": "", |  | ||||||
|         "requests": { |  | ||||||
|             "ui_target": "context_menu_plugins", |  | ||||||
|             "pass_fm_events": "true" |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,220 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <!-- Generated with glade 3.38.2 --> |  | ||||||
| <interface> |  | ||||||
|   <requires lib="gtk+" version="3.16"/> |  | ||||||
|   <object class="GtkImage" id="image1"> |  | ||||||
|     <property name="visible">True</property> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="stock">gtk-media-play</property> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkAdjustment" id="scrub_step_adjuster"> |  | ||||||
|     <property name="lower">1</property> |  | ||||||
|     <property name="upper">100</property> |  | ||||||
|     <property name="value">65</property> |  | ||||||
|     <property name="step-increment">1</property> |  | ||||||
|     <property name="page-increment">10</property> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkTextBuffer" id="textbuffer"/> |  | ||||||
|   <object class="GtkDialog" id="info_dialog"> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="border-width">6</property> |  | ||||||
|     <property name="title" translatable="yes">Movie / TV Info</property> |  | ||||||
|     <property name="modal">True</property> |  | ||||||
|     <property name="window-position">center-on-parent</property> |  | ||||||
|     <property name="default-width">420</property> |  | ||||||
|     <property name="destroy-with-parent">True</property> |  | ||||||
|     <property name="type-hint">dialog</property> |  | ||||||
|     <property name="skip-taskbar-hint">True</property> |  | ||||||
|     <property name="skip-pager-hint">True</property> |  | ||||||
|     <property name="gravity">center</property> |  | ||||||
|     <child internal-child="vbox"> |  | ||||||
|       <object class="GtkBox" id="dialog_vbox"> |  | ||||||
|         <property name="visible">True</property> |  | ||||||
|         <property name="can-focus">False</property> |  | ||||||
|         <property name="spacing">12</property> |  | ||||||
|         <child internal-child="action_area"> |  | ||||||
|           <object class="GtkButtonBox" id="dialog_action_area"> |  | ||||||
|             <property name="visible">True</property> |  | ||||||
|             <property name="can-focus">False</property> |  | ||||||
|             <property name="layout-style">end</property> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkButton" id="cancel_button"> |  | ||||||
|                 <property name="label">gtk-close</property> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="can-default">True</property> |  | ||||||
|                 <property name="receives-default">False</property> |  | ||||||
|                 <property name="use-stock">True</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">True</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">0</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|           <packing> |  | ||||||
|             <property name="expand">False</property> |  | ||||||
|             <property name="fill">False</property> |  | ||||||
|             <property name="pack-type">end</property> |  | ||||||
|             <property name="position">0</property> |  | ||||||
|           </packing> |  | ||||||
|         </child> |  | ||||||
|         <child> |  | ||||||
|           <object class="GtkBox"> |  | ||||||
|             <property name="visible">True</property> |  | ||||||
|             <property name="can-focus">False</property> |  | ||||||
|             <property name="orientation">vertical</property> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkImage" id="thumbnail_preview_img"> |  | ||||||
|                 <property name="height-request">320</property> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="margin-start">5</property> |  | ||||||
|                 <property name="margin-end">5</property> |  | ||||||
|                 <property name="margin-top">5</property> |  | ||||||
|                 <property name="stock">gtk-missing-image</property> |  | ||||||
|                 <property name="icon_size">6</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">True</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">0</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkBox"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="orientation">vertical</property> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkTextView" id="overview_textview"> |  | ||||||
|                     <property name="height-request">120</property> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">True</property> |  | ||||||
|                     <property name="margin-start">5</property> |  | ||||||
|                     <property name="margin-end">5</property> |  | ||||||
|                     <property name="margin-top">10</property> |  | ||||||
|                     <property name="margin-bottom">10</property> |  | ||||||
|                     <property name="wrap-mode">word</property> |  | ||||||
|                     <property name="cursor-visible">False</property> |  | ||||||
|                     <property name="buffer">textbuffer</property> |  | ||||||
|                     <property name="overwrite">True</property> |  | ||||||
|                     <property name="monospace">True</property> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">True</property> |  | ||||||
|                     <property name="fill">True</property> |  | ||||||
|                     <property name="position">0</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">False</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">1</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkTable" id="general_table"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="border-width">4</property> |  | ||||||
|                 <property name="n-rows">3</property> |  | ||||||
|                 <property name="n-columns">2</property> |  | ||||||
|                 <property name="column-spacing">12</property> |  | ||||||
|                 <property name="row-spacing">6</property> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkLinkButton" id="trailer_link"> |  | ||||||
|                     <property name="label" translatable="yes">Trailer</property> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">True</property> |  | ||||||
|                     <property name="receives-default">True</property> |  | ||||||
|                     <property name="image">image1</property> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="right-attach">2</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkLabel"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="label" translatable="yes"><b>File _Name:</b></property> |  | ||||||
|                     <property name="use-markup">True</property> |  | ||||||
|                     <property name="use-underline">True</property> |  | ||||||
|                     <property name="xalign">0</property> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="top-attach">1</property> |  | ||||||
|                     <property name="bottom-attach">2</property> |  | ||||||
|                     <property name="x-options">GTK_FILL</property> |  | ||||||
|                     <property name="y-options"/> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkLabel"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="label" translatable="yes"><b>_Location:</b></property> |  | ||||||
|                     <property name="use-markup">True</property> |  | ||||||
|                     <property name="use-underline">True</property> |  | ||||||
|                     <property name="xalign">0</property> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="top-attach">2</property> |  | ||||||
|                     <property name="bottom-attach">3</property> |  | ||||||
|                     <property name="x-options">GTK_FILL</property> |  | ||||||
|                     <property name="y-options"/> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkEntry" id="file_name"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">True</property> |  | ||||||
|                     <property name="editable">False</property> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="left-attach">1</property> |  | ||||||
|                     <property name="right-attach">2</property> |  | ||||||
|                     <property name="top-attach">1</property> |  | ||||||
|                     <property name="bottom-attach">2</property> |  | ||||||
|                     <property name="y-options"/> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkEntry" id="file_location"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">True</property> |  | ||||||
|                     <property name="editable">False</property> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="left-attach">1</property> |  | ||||||
|                     <property name="right-attach">2</property> |  | ||||||
|                     <property name="top-attach">2</property> |  | ||||||
|                     <property name="bottom-attach">3</property> |  | ||||||
|                     <property name="x-options">GTK_FILL</property> |  | ||||||
|                     <property name="y-options"/> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">False</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">2</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|           <packing> |  | ||||||
|             <property name="expand">True</property> |  | ||||||
|             <property name="fill">True</property> |  | ||||||
|             <property name="position">1</property> |  | ||||||
|           </packing> |  | ||||||
|         </child> |  | ||||||
|       </object> |  | ||||||
|     </child> |  | ||||||
|     <action-widgets> |  | ||||||
|       <action-widget response="-7">cancel_button</action-widget> |  | ||||||
|     </action-widgets> |  | ||||||
|   </object> |  | ||||||
| </interface> |  | ||||||
| @@ -1,187 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os |  | ||||||
| import threading |  | ||||||
| import subprocess |  | ||||||
| import inspect |  | ||||||
| import requests |  | ||||||
| import shutil |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| gi.require_version('GdkPixbuf', '2.0') |  | ||||||
| from gi.repository import Gtk |  | ||||||
| from gi.repository import GLib |  | ||||||
| from gi.repository import GdkPixbuf |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from plugins.plugin_base import PluginBase |  | ||||||
| from .tmdbscraper import scraper |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # 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.name                   = "Movie/TV Info"   # NOTE: Need to remove after establishing private bidirectional 1-1 message bus |  | ||||||
|                                                         #       where self.name should not be needed for message comms |  | ||||||
|         self._GLADE_FILE            = f"{self.path}/movie_tv_info.glade" |  | ||||||
|  |  | ||||||
|         self._dialog                = None |  | ||||||
|         self._thumbnail_preview_img = None |  | ||||||
|         self._tmdb                  = scraper.get_tmdb_scraper() |  | ||||||
|         self._overview              = None |  | ||||||
|         self._file_name             = None |  | ||||||
|         self._file_location         = None |  | ||||||
|         self._trailer_link          = None |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def run(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) |  | ||||||
|  |  | ||||||
|         self._thumbnailer_dialog    = self._builder.get_object("info_dialog") |  | ||||||
|         self._overview              = self._builder.get_object("textbuffer") |  | ||||||
|         self._file_name             = self._builder.get_object("file_name") |  | ||||||
|         self._file_location         = self._builder.get_object("file_location") |  | ||||||
|         self._thumbnail_preview_img = self._builder.get_object("thumbnail_preview_img") |  | ||||||
|         self._file_hash             = self._builder.get_object("file_hash") |  | ||||||
|         self._trailer_link          = self._builder.get_object("trailer_link") |  | ||||||
|  |  | ||||||
|     def generate_reference_ui_element(self): |  | ||||||
|         item = Gtk.ImageMenuItem(self.name) |  | ||||||
|         item.set_image( Gtk.Image(stock=Gtk.STOCK_FIND) ) |  | ||||||
|         item.connect("activate", self._show_info_page) |  | ||||||
|         item.set_always_show_image(True) |  | ||||||
|         return item |  | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def _show_info_page(self, widget=None, eve=None): |  | ||||||
|         self._event_system.emit("get_current_state") |  | ||||||
|  |  | ||||||
|         state               = self._fm_state |  | ||||||
|         self._event_message = None |  | ||||||
|  |  | ||||||
|         GLib.idle_add(self._process_changes, (state)) |  | ||||||
|  |  | ||||||
|     def _process_changes(self, state): |  | ||||||
|         self._fm_state = None |  | ||||||
|  |  | ||||||
|         if len(state.selected_files) == 1: |  | ||||||
|             self._fm_state = state |  | ||||||
|             self._set_ui_data() |  | ||||||
|             response   = self._thumbnailer_dialog.run() |  | ||||||
|             if response in [Gtk.ResponseType.CLOSE, Gtk.ResponseType.DELETE_EVENT]: |  | ||||||
|                 self._thumbnailer_dialog.hide() |  | ||||||
|  |  | ||||||
|     def _set_ui_data(self): |  | ||||||
|         title, path, trailer, video_data = self.get_video_data() |  | ||||||
|         keys = video_data.keys() if video_data else None |  | ||||||
|  |  | ||||||
|         overview_text  = video_data["overview"] if video_data else f"...NO {self.name.upper()} DATA..." |  | ||||||
|  |  | ||||||
|         self.set_text_data(title, path, overview_text) |  | ||||||
|         self.set_thumbnail(video_data) if video_data else ... |  | ||||||
|         self.set_trailer_link(trailer) |  | ||||||
|  |  | ||||||
|         print(video_data["videos"]) if not keys in ("", None) and "videos" in keys else ... |  | ||||||
|  |  | ||||||
|     def get_video_data(self): |  | ||||||
|         uri            = self._fm_state.selected_files[0] |  | ||||||
|         path           = self._fm_state.tab.get_current_directory() |  | ||||||
|         parts          = uri.split("/") |  | ||||||
|         _title         = parts[ len(parts) - 1 ] |  | ||||||
|         trailer        = None |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             title          = _title.split("(")[0].strip() |  | ||||||
|             startIndex     = _title.index('(') + 1 |  | ||||||
|             endIndex       = _title.index(')') |  | ||||||
|             date           = title[startIndex:endIndex] |  | ||||||
|         except Exception as e: |  | ||||||
|             print(repr(e)) |  | ||||||
|             title          = _title |  | ||||||
|             date           = None |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|  |  | ||||||
|             video_data    = self._tmdb.search(title, date)[0] |  | ||||||
|             video_id      = video_data["id"] |  | ||||||
|             try: |  | ||||||
|                 results = self._tmdb.tmdbapi.get_movie(str(video_id), append_to_response="videos")["videos"]["results"] |  | ||||||
|                 for result in results: |  | ||||||
|                     if "YouTube" in result["site"]: |  | ||||||
|                         trailer = result["key"] |  | ||||||
|  |  | ||||||
|                 if not trailer: |  | ||||||
|                     raise Exception("No key found. Defering to none...") |  | ||||||
|             except Exception as e: |  | ||||||
|                 print("No trailer found...") |  | ||||||
|  |  | ||||||
|         except Exception as e: |  | ||||||
|             print(repr(e)) |  | ||||||
|             video_data    = None |  | ||||||
|  |  | ||||||
|         return title, path, trailer, video_data |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def set_text_data(self, title, path, overview_text): |  | ||||||
|         self._file_name.set_text(title) |  | ||||||
|         self._file_location.set_text(path) |  | ||||||
|         self._overview.set_text(overview_text) |  | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def set_thumbnail(self, video_data): |  | ||||||
|         background_url = video_data["backdrop_path"] |  | ||||||
|         # background_url = video_data["poster_path"] |  | ||||||
|         background_pth = "/tmp/sfm_mvtv_info.jpg" |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             os.remove(background_pth) |  | ||||||
|         except Exception as e: |  | ||||||
|             ... |  | ||||||
|  |  | ||||||
|         r = requests.get(background_url, stream = True) |  | ||||||
|  |  | ||||||
|         if r.status_code == 200: |  | ||||||
|             r.raw.decode_content = True |  | ||||||
|             with open(background_pth,'wb') as f: |  | ||||||
|                 shutil.copyfileobj(r.raw, f) |  | ||||||
|  |  | ||||||
|             print('Cover Background Image sucessfully retreived...') |  | ||||||
|             preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(background_pth) |  | ||||||
|             self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf) |  | ||||||
|         else: |  | ||||||
|             print('Cover Background Image Couldn\'t be retreived...') |  | ||||||
|  |  | ||||||
|     def set_trailer_link(self, trailer): |  | ||||||
|         if trailer: |  | ||||||
|             self._trailer_link.set_uri(f"https://www.youtube.com/watch?v={trailer}") |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Module |  | ||||||
| """ |  | ||||||
| @@ -1,22 +0,0 @@ | |||||||
| import json |  | ||||||
| import sys |  | ||||||
|  |  | ||||||
| from .lib.tmdbscraper.tmdb import TMDBMovieScraper |  | ||||||
| from .lib.tmdbscraper.fanarttv import get_details as get_fanarttv_artwork |  | ||||||
| from .lib.tmdbscraper.imdbratings import get_details as get_imdb_details |  | ||||||
| from .lib.tmdbscraper.traktratings import get_trakt_ratinginfo |  | ||||||
| from .scraper_datahelper import combine_scraped_details_info_and_ratings, \ |  | ||||||
|     combine_scraped_details_available_artwork, find_uniqueids_in_text, get_params |  | ||||||
| from .scraper_config import configure_scraped_details, PathSpecificSettings, \ |  | ||||||
|     configure_tmdb_artwork, is_fanarttv_configured |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_tmdb_scraper(): |  | ||||||
|     language       = 'en-US' |  | ||||||
|     certcountry    = 'us' |  | ||||||
|     ADDON_SETTINGS = None |  | ||||||
|     return TMDBMovieScraper(ADDON_SETTINGS, language, certcountry) |  | ||||||
| @@ -1,111 +0,0 @@ | |||||||
| def configure_scraped_details(details, settings): |  | ||||||
|     details = _configure_rating_prefix(details, settings) |  | ||||||
|     details = _configure_keeporiginaltitle(details, settings) |  | ||||||
|     details = _configure_trailer(details, settings) |  | ||||||
|     details = _configure_multiple_studios(details, settings) |  | ||||||
|     details = _configure_default_rating(details, settings) |  | ||||||
|     details = _configure_tags(details, settings) |  | ||||||
|     return details |  | ||||||
|  |  | ||||||
| def configure_tmdb_artwork(details, settings): |  | ||||||
|     if 'available_art' not in details: |  | ||||||
|         return details |  | ||||||
|  |  | ||||||
|     art = details['available_art'] |  | ||||||
|     fanart_enabled = settings.getSettingBool('fanart') |  | ||||||
|     if not fanart_enabled: |  | ||||||
|         if 'fanart' in art: |  | ||||||
|             del art['fanart'] |  | ||||||
|         if 'set.fanart' in art: |  | ||||||
|             del art['set.fanart'] |  | ||||||
|     if not settings.getSettingBool('landscape'): |  | ||||||
|         if 'landscape' in art: |  | ||||||
|             if fanart_enabled: |  | ||||||
|                 art['fanart'] = art.get('fanart', []) + art['landscape'] |  | ||||||
|             del art['landscape'] |  | ||||||
|         if 'set.landscape' in art: |  | ||||||
|             if fanart_enabled: |  | ||||||
|                 art['set.fanart'] = art.get('set.fanart', []) + art['set.landscape'] |  | ||||||
|             del art['set.landscape'] |  | ||||||
|  |  | ||||||
|     return details |  | ||||||
|  |  | ||||||
| def is_fanarttv_configured(settings): |  | ||||||
|     return settings.getSettingBool('enable_fanarttv_artwork') |  | ||||||
|  |  | ||||||
| def _configure_rating_prefix(details, settings): |  | ||||||
|     if details['info'].get('mpaa'): |  | ||||||
|         details['info']['mpaa'] = settings.getSettingString('certprefix') + details['info']['mpaa'] |  | ||||||
|     return details |  | ||||||
|  |  | ||||||
| def _configure_keeporiginaltitle(details, settings): |  | ||||||
|     if settings.getSettingBool('keeporiginaltitle'): |  | ||||||
|         details['info']['title'] = details['info']['originaltitle'] |  | ||||||
|     return details |  | ||||||
|  |  | ||||||
| def _configure_trailer(details, settings): |  | ||||||
|     if details['info'].get('trailer') and not settings.getSettingBool('trailer'): |  | ||||||
|         del details['info']['trailer'] |  | ||||||
|     return details |  | ||||||
|  |  | ||||||
| def _configure_multiple_studios(details, settings): |  | ||||||
|     if not settings.getSettingBool('multiple_studios'): |  | ||||||
|         details['info']['studio'] = details['info']['studio'][:1] |  | ||||||
|     return details |  | ||||||
|  |  | ||||||
| def _configure_default_rating(details, settings): |  | ||||||
|     imdb_default = bool(details['ratings'].get('imdb')) and settings.getSettingString('RatingS') == 'IMDb' |  | ||||||
|     trakt_default = bool(details['ratings'].get('trakt')) and settings.getSettingString('RatingS') == 'Trakt' |  | ||||||
|     default_rating = 'themoviedb' |  | ||||||
|     if imdb_default: |  | ||||||
|         default_rating = 'imdb' |  | ||||||
|     elif trakt_default: |  | ||||||
|         default_rating = 'trakt' |  | ||||||
|     if default_rating not in details['ratings']: |  | ||||||
|         default_rating = list(details['ratings'].keys())[0] if details['ratings'] else None |  | ||||||
|     for rating_type in details['ratings'].keys(): |  | ||||||
|         details['ratings'][rating_type]['default'] = rating_type == default_rating |  | ||||||
|     return details |  | ||||||
|  |  | ||||||
| def _configure_tags(details, settings): |  | ||||||
|     if not settings.getSettingBool('add_tags'): |  | ||||||
|         del details['info']['tag'] |  | ||||||
|     return details |  | ||||||
|  |  | ||||||
| # pylint: disable=invalid-name |  | ||||||
| try: |  | ||||||
|     basestring |  | ||||||
| except NameError: # py2 / py3 |  | ||||||
|     basestring = str |  | ||||||
|  |  | ||||||
| #pylint: disable=redefined-builtin |  | ||||||
| class PathSpecificSettings(object): |  | ||||||
|     # read-only shim for typed `xbmcaddon.Addon().getSetting*` methods |  | ||||||
|     def __init__(self, settings_dict, log_fn): |  | ||||||
|         self.data = settings_dict |  | ||||||
|         self.log = log_fn |  | ||||||
|  |  | ||||||
|     def getSettingBool(self, id): |  | ||||||
|         return self._inner_get_setting(id, bool, False) |  | ||||||
|  |  | ||||||
|     def getSettingInt(self, id): |  | ||||||
|         return self._inner_get_setting(id, int, 0) |  | ||||||
|  |  | ||||||
|     def getSettingNumber(self, id): |  | ||||||
|         return self._inner_get_setting(id, float, 0.0) |  | ||||||
|  |  | ||||||
|     def getSettingString(self, id): |  | ||||||
|         return self._inner_get_setting(id, basestring, '') |  | ||||||
|  |  | ||||||
|     def _inner_get_setting(self, setting_id, setting_type, default): |  | ||||||
|         value = self.data.get(setting_id) |  | ||||||
|         if isinstance(value, setting_type): |  | ||||||
|             return value |  | ||||||
|         self._log_bad_value(value, setting_id) |  | ||||||
|         return default |  | ||||||
|  |  | ||||||
|     def _log_bad_value(self, value, setting_id): |  | ||||||
|         if value is None: |  | ||||||
|             self.log("requested setting ({0}) was not found.".format(setting_id)) |  | ||||||
|         else: |  | ||||||
|             self.log('failed to load value "{0}" for setting {1}'.format(value, setting_id)) |  | ||||||
| @@ -1,54 +0,0 @@ | |||||||
| import re |  | ||||||
| try: |  | ||||||
|     from urlparse import parse_qsl |  | ||||||
| except ImportError: # py2 / py3 |  | ||||||
|     from urllib.parse import parse_qsl |  | ||||||
|  |  | ||||||
| # get addon params from the plugin path querystring |  | ||||||
| def get_params(argv): |  | ||||||
|     result = {'handle': int(argv[0])} |  | ||||||
|     if len(argv) < 2 or not argv[1]: |  | ||||||
|         return result |  | ||||||
|  |  | ||||||
|     result.update(parse_qsl(argv[1].lstrip('?'))) |  | ||||||
|     return result |  | ||||||
|  |  | ||||||
| def combine_scraped_details_info_and_ratings(original_details, additional_details): |  | ||||||
|     def update_or_set(details, key, value): |  | ||||||
|         if key in details: |  | ||||||
|             details[key].update(value) |  | ||||||
|         else: |  | ||||||
|             details[key] = value |  | ||||||
|  |  | ||||||
|     if additional_details: |  | ||||||
|         if additional_details.get('info'): |  | ||||||
|             update_or_set(original_details, 'info', additional_details['info']) |  | ||||||
|         if additional_details.get('ratings'): |  | ||||||
|             update_or_set(original_details, 'ratings', additional_details['ratings']) |  | ||||||
|     return original_details |  | ||||||
|  |  | ||||||
| def combine_scraped_details_available_artwork(original_details, additional_details): |  | ||||||
|     if additional_details and additional_details.get('available_art'): |  | ||||||
|         available_art = additional_details['available_art'] |  | ||||||
|         if not original_details.get('available_art'): |  | ||||||
|             original_details['available_art'] = available_art |  | ||||||
|         else: |  | ||||||
|             for arttype, artlist in available_art.items(): |  | ||||||
|                 original_details['available_art'][arttype] = \ |  | ||||||
|                     artlist + original_details['available_art'].get(arttype, []) |  | ||||||
|  |  | ||||||
|     return original_details |  | ||||||
|  |  | ||||||
| def find_uniqueids_in_text(input_text): |  | ||||||
|     result = {} |  | ||||||
|     res = re.search(r'(themoviedb.org/movie/)([0-9]+)', input_text) |  | ||||||
|     if (res): |  | ||||||
|         result['tmdb'] = res.group(2) |  | ||||||
|     res = re.search(r'imdb....?/title/tt([0-9]+)', input_text) |  | ||||||
|     if (res): |  | ||||||
|         result['imdb'] = 'tt' + res.group(1) |  | ||||||
|     else: |  | ||||||
|         res = re.search(r'imdb....?/Title\?t{0,2}([0-9]+)', input_text) |  | ||||||
|         if (res): |  | ||||||
|             result['imdb'] = 'tt' + res.group(1) |  | ||||||
|     return result |  | ||||||
| @@ -1,175 +0,0 @@ | |||||||
| import json |  | ||||||
| import sys |  | ||||||
| import xbmc |  | ||||||
| import xbmcaddon |  | ||||||
| import xbmcgui |  | ||||||
| import xbmcplugin |  | ||||||
|  |  | ||||||
| from lib.tmdbscraper.tmdb import TMDBMovieScraper |  | ||||||
| from lib.tmdbscraper.fanarttv import get_details as get_fanarttv_artwork |  | ||||||
| from lib.tmdbscraper.imdbratings import get_details as get_imdb_details |  | ||||||
| from lib.tmdbscraper.traktratings import get_trakt_ratinginfo |  | ||||||
| from scraper_datahelper import combine_scraped_details_info_and_ratings, \ |  | ||||||
|     combine_scraped_details_available_artwork, find_uniqueids_in_text, get_params |  | ||||||
| from scraper_config import configure_scraped_details, PathSpecificSettings, \ |  | ||||||
|     configure_tmdb_artwork, is_fanarttv_configured |  | ||||||
|  |  | ||||||
| ADDON_SETTINGS = xbmcaddon.Addon() |  | ||||||
| ID = ADDON_SETTINGS.getAddonInfo('id') |  | ||||||
|  |  | ||||||
| def log(msg, level=xbmc.LOGDEBUG): |  | ||||||
|     xbmc.log(msg='[{addon}]: {msg}'.format(addon=ID, msg=msg), level=level) |  | ||||||
|  |  | ||||||
| def get_tmdb_scraper(settings): |  | ||||||
|     language = settings.getSettingString('language') |  | ||||||
|     certcountry = settings.getSettingString('tmdbcertcountry') |  | ||||||
|     return TMDBMovieScraper(ADDON_SETTINGS, language, certcountry) |  | ||||||
|  |  | ||||||
| def search_for_movie(title, year, handle, settings): |  | ||||||
|     log("Find movie with title '{title}' from year '{year}'".format(title=title, year=year), xbmc.LOGINFO) |  | ||||||
|     title = _strip_trailing_article(title) |  | ||||||
|     search_results = get_tmdb_scraper(settings).search(title, year) |  | ||||||
|     if not search_results: |  | ||||||
|         return |  | ||||||
|     if 'error' in search_results: |  | ||||||
|         header = "The Movie Database Python error searching with web service TMDB" |  | ||||||
|         xbmcgui.Dialog().notification(header, search_results['error'], xbmcgui.NOTIFICATION_WARNING) |  | ||||||
|         log(header + ': ' + search_results['error'], xbmc.LOGWARNING) |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     for movie in search_results: |  | ||||||
|         listitem = _searchresult_to_listitem(movie) |  | ||||||
|         uniqueids = {'tmdb': str(movie['id'])} |  | ||||||
|         xbmcplugin.addDirectoryItem(handle=handle, url=build_lookup_string(uniqueids), |  | ||||||
|             listitem=listitem, isFolder=True) |  | ||||||
|  |  | ||||||
| _articles = [prefix + article for prefix in (', ', ' ') for article in ("the", "a", "an")] |  | ||||||
| def _strip_trailing_article(title): |  | ||||||
|     title = title.lower() |  | ||||||
|     for article in _articles: |  | ||||||
|         if title.endswith(article): |  | ||||||
|             return title[:-len(article)] |  | ||||||
|     return title |  | ||||||
|  |  | ||||||
| def _searchresult_to_listitem(movie): |  | ||||||
|     movie_info = {'title': movie['title']} |  | ||||||
|     movie_label = movie['title'] |  | ||||||
|  |  | ||||||
|     movie_year = movie['release_date'].split('-')[0] if movie.get('release_date') else None |  | ||||||
|     if movie_year: |  | ||||||
|         movie_label += ' ({})'.format(movie_year) |  | ||||||
|         movie_info['year'] = movie_year |  | ||||||
|  |  | ||||||
|     listitem = xbmcgui.ListItem(movie_label, offscreen=True) |  | ||||||
|  |  | ||||||
|     listitem.setInfo('video', movie_info) |  | ||||||
|     if movie['poster_path']: |  | ||||||
|         listitem.setArt({'thumb': movie['poster_path']}) |  | ||||||
|  |  | ||||||
|     return listitem |  | ||||||
|  |  | ||||||
| # Low limit because a big list of artwork can cause trouble in some cases |  | ||||||
| # (a column can be too large for the MySQL integration), |  | ||||||
| # and how useful is a big list anyway? Not exactly rhetorical, this is an experiment. |  | ||||||
| IMAGE_LIMIT = 10 |  | ||||||
|  |  | ||||||
| def add_artworks(listitem, artworks): |  | ||||||
|     for arttype, artlist in artworks.items(): |  | ||||||
|         if arttype == 'fanart': |  | ||||||
|             continue |  | ||||||
|         for image in artlist[:IMAGE_LIMIT]: |  | ||||||
|             listitem.addAvailableArtwork(image['url'], arttype) |  | ||||||
|  |  | ||||||
|     fanart_to_set = [{'image': image['url'], 'preview': image['preview']} |  | ||||||
|         for image in artworks['fanart'][:IMAGE_LIMIT]] |  | ||||||
|     listitem.setAvailableFanart(fanart_to_set) |  | ||||||
|  |  | ||||||
| def get_details(input_uniqueids, handle, settings): |  | ||||||
|     if not input_uniqueids: |  | ||||||
|         return False |  | ||||||
|     details = get_tmdb_scraper(settings).get_details(input_uniqueids) |  | ||||||
|     if not details: |  | ||||||
|         return False |  | ||||||
|     if 'error' in details: |  | ||||||
|         header = "The Movie Database Python error with web service TMDB" |  | ||||||
|         xbmcgui.Dialog().notification(header, details['error'], xbmcgui.NOTIFICATION_WARNING) |  | ||||||
|         log(header + ': ' + details['error'], xbmc.LOGWARNING) |  | ||||||
|         return False |  | ||||||
|  |  | ||||||
|     details = configure_tmdb_artwork(details, settings) |  | ||||||
|  |  | ||||||
|     if settings.getSettingString('RatingS') == 'IMDb' or settings.getSettingBool('imdbanyway'): |  | ||||||
|         imdbinfo = get_imdb_details(details['uniqueids']) |  | ||||||
|         if 'error' in imdbinfo: |  | ||||||
|             header = "The Movie Database Python error with website IMDB" |  | ||||||
|             log(header + ': ' + imdbinfo['error'], xbmc.LOGWARNING) |  | ||||||
|         else: |  | ||||||
|             details = combine_scraped_details_info_and_ratings(details, imdbinfo) |  | ||||||
|  |  | ||||||
|     if settings.getSettingString('RatingS') == 'Trakt' or settings.getSettingBool('traktanyway'): |  | ||||||
|         traktinfo = get_trakt_ratinginfo(details['uniqueids']) |  | ||||||
|         details = combine_scraped_details_info_and_ratings(details, traktinfo) |  | ||||||
|  |  | ||||||
|     if is_fanarttv_configured(settings): |  | ||||||
|         fanarttv_info = get_fanarttv_artwork(details['uniqueids'], |  | ||||||
|             settings.getSettingString('fanarttv_clientkey'), |  | ||||||
|             settings.getSettingString('fanarttv_language'), |  | ||||||
|             details['_info']['set_tmdbid']) |  | ||||||
|         details = combine_scraped_details_available_artwork(details, fanarttv_info) |  | ||||||
|  |  | ||||||
|     details = configure_scraped_details(details, settings) |  | ||||||
|  |  | ||||||
|     listitem = xbmcgui.ListItem(details['info']['title'], offscreen=True) |  | ||||||
|     listitem.setInfo('video', details['info']) |  | ||||||
|     listitem.setCast(details['cast']) |  | ||||||
|     listitem.setUniqueIDs(details['uniqueids'], 'tmdb') |  | ||||||
|     add_artworks(listitem, details['available_art']) |  | ||||||
|  |  | ||||||
|     for rating_type, value in details['ratings'].items(): |  | ||||||
|         if 'votes' in value: |  | ||||||
|             listitem.setRating(rating_type, value['rating'], value['votes'], value['default']) |  | ||||||
|         else: |  | ||||||
|             listitem.setRating(rating_type, value['rating'], defaultt=value['default']) |  | ||||||
|  |  | ||||||
|     xbmcplugin.setResolvedUrl(handle=handle, succeeded=True, listitem=listitem) |  | ||||||
|     return True |  | ||||||
|  |  | ||||||
| def find_uniqueids_in_nfo(nfo, handle): |  | ||||||
|     uniqueids = find_uniqueids_in_text(nfo) |  | ||||||
|     if uniqueids: |  | ||||||
|         listitem = xbmcgui.ListItem(offscreen=True) |  | ||||||
|         xbmcplugin.addDirectoryItem( |  | ||||||
|             handle=handle, url=build_lookup_string(uniqueids), listitem=listitem, isFolder=True) |  | ||||||
|  |  | ||||||
| def build_lookup_string(uniqueids): |  | ||||||
|     return json.dumps(uniqueids) |  | ||||||
|  |  | ||||||
| def parse_lookup_string(uniqueids): |  | ||||||
|     try: |  | ||||||
|         return json.loads(uniqueids) |  | ||||||
|     except ValueError: |  | ||||||
|         log("Can't parse this lookup string, is it from another add-on?\n" + uniqueids, xbmc.LOGWARNING) |  | ||||||
|         return None |  | ||||||
|  |  | ||||||
| def run(): |  | ||||||
|     params = get_params(sys.argv[1:]) |  | ||||||
|     enddir = True |  | ||||||
|     if 'action' in params: |  | ||||||
|         settings = ADDON_SETTINGS if not params.get('pathSettings') else \ |  | ||||||
|             PathSpecificSettings(json.loads(params['pathSettings']), lambda msg: log(msg, xbmc.LOGWARNING)) |  | ||||||
|         action = params["action"] |  | ||||||
|         if action == 'find' and 'title' in params: |  | ||||||
|             search_for_movie(params["title"], params.get("year"), params['handle'], settings) |  | ||||||
|         elif action == 'getdetails' and 'url' in params: |  | ||||||
|             enddir = not get_details(parse_lookup_string(params["url"]), params['handle'], settings) |  | ||||||
|         elif action == 'NfoUrl' and 'nfo' in params: |  | ||||||
|             find_uniqueids_in_nfo(params["nfo"], params['handle']) |  | ||||||
|         else: |  | ||||||
|             log("unhandled action: " + action, xbmc.LOGWARNING) |  | ||||||
|     else: |  | ||||||
|         log("No action in 'params' to act on", xbmc.LOGWARNING) |  | ||||||
|     if enddir: |  | ||||||
|         xbmcplugin.endOfDirectory(params['handle']) |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     run() |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Module |  | ||||||
| """ |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Package |  | ||||||
| """ |  | ||||||
| @@ -1,13 +0,0 @@ | |||||||
| { |  | ||||||
|     "manifest": { |  | ||||||
|         "name": "Search", |  | ||||||
|         "author": "ITDominator", |  | ||||||
|         "version": "0.0.1", |  | ||||||
|         "support": "", |  | ||||||
|         "requests": { |  | ||||||
|             "ui_target": "context_menu", |  | ||||||
|             "pass_fm_events": "true", |  | ||||||
|             "bind_keys": ["Search||show_search_page:<Control>s"] |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Mixins Module |  | ||||||
| """ |  | ||||||
| @@ -1,79 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import threading |  | ||||||
| import subprocess |  | ||||||
| import signal |  | ||||||
| import json |  | ||||||
| import shlex |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk |  | ||||||
| from gi.repository import GLib |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from ..widgets.file_preview_widget import FilePreviewWidget |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # 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 FileSearchMixin: |  | ||||||
|     def _run_find_file_query(self, widget=None, eve=None): |  | ||||||
|         self._handle_find_file_query(query=widget) |  | ||||||
|  |  | ||||||
|     # TODO: Merge this logic with nearly the exact same thing in grep_search_mixin |  | ||||||
|     @daemon_threaded |  | ||||||
|     def _handle_find_file_query(self, widget=None, eve=None, query=None): |  | ||||||
|         # NOTE: Freeze IPC consumption |  | ||||||
|         self.pause_fifo_update = True |  | ||||||
|         self.search_query      = "" |  | ||||||
|  |  | ||||||
|         # NOTE: Kill the former process |  | ||||||
|         if self._list_proc: |  | ||||||
|             if self._list_proc.poll() == None: |  | ||||||
|                 self._list_proc.terminate() |  | ||||||
|                 while self._list_proc.poll() == None: |  | ||||||
|                     ... |  | ||||||
|  |  | ||||||
|             self._list_proc = None |  | ||||||
|  |  | ||||||
|         # NOTE: Clear children from ui and make sure ui thread redraws |  | ||||||
|         GLib.idle_add(self.reset_file_list_box) |  | ||||||
|  |  | ||||||
|         # NOTE: If query create new process and do all new loop. |  | ||||||
|         if query: |  | ||||||
|             self.pause_fifo_update = False |  | ||||||
|             GLib.idle_add(self._exec_find_file_query, query) |  | ||||||
|  |  | ||||||
|     def _exec_find_file_query(self, widget=None, eve=None): |  | ||||||
|         query = widget.get_text() |  | ||||||
|  |  | ||||||
|         if not query in ("", None): |  | ||||||
|             self.search_query = query |  | ||||||
|             target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) |  | ||||||
|             command = ["python", f"{self.path}/utils/search.py", "-t", "file_search", "-d", f"{target_dir}", "-q", f"{query}"] |  | ||||||
|             self._list_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) |  | ||||||
|  |  | ||||||
|     def _load_file_ui(self, data): |  | ||||||
|         Gtk.main_iteration() |  | ||||||
|  |  | ||||||
|         if not data in ("", None): |  | ||||||
|             jdata  = json.loads( data ) |  | ||||||
|             target = jdata[0] |  | ||||||
|             file   = jdata[1] |  | ||||||
|  |  | ||||||
|             widget = FilePreviewWidget(target, file) |  | ||||||
|             self._file_list.add(widget) |  | ||||||
| @@ -1,83 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import ctypes |  | ||||||
| import threading |  | ||||||
| import subprocess |  | ||||||
| import signal |  | ||||||
| import json |  | ||||||
| import shlex |  | ||||||
| libgcc_s = ctypes.CDLL('libgcc_s.so.1') |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk |  | ||||||
| from gi.repository import GLib |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from ..widgets.grep_preview_widget import GrepPreviewWidget |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # 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 GrepSearchMixin: |  | ||||||
|     def _run_grep_query(self, widget=None, eve=None): |  | ||||||
|         self._handle_grep_query(query=widget) |  | ||||||
|  |  | ||||||
|     # TODO: Merge this logic with nearly the exact same thing in file_search_mixin |  | ||||||
|     @daemon_threaded |  | ||||||
|     def _handle_grep_query(self, widget=None, eve=None, query=None): |  | ||||||
|         # NOTE: Freeze IPC consumption |  | ||||||
|         self.pause_fifo_update = True |  | ||||||
|         self.grep_query        = "" |  | ||||||
|  |  | ||||||
|         # NOTE: Kill the former process |  | ||||||
|         if self._grep_proc: |  | ||||||
|             if self._grep_proc.poll() == None: |  | ||||||
|                 self._grep_proc.terminate() |  | ||||||
|                 while self._grep_proc.poll() == None: |  | ||||||
|                     ... |  | ||||||
|  |  | ||||||
|             self._grep_proc = None |  | ||||||
|  |  | ||||||
|         # NOTE: Clear children from ui and make sure ui thread redraws |  | ||||||
|         GLib.idle_add(self.reset_grep_box) |  | ||||||
|  |  | ||||||
|         # NOTE: If query create new process and do all new loop. |  | ||||||
|         if query: |  | ||||||
|             self.pause_fifo_update = False |  | ||||||
|             GLib.idle_add(self._exec_grep_query, query) |  | ||||||
|  |  | ||||||
|     def _exec_grep_query(self, widget=None, eve=None): |  | ||||||
|         query = widget.get_text() |  | ||||||
|         if not query.strip() in ("", None): |  | ||||||
|             self.grep_query = query |  | ||||||
|  |  | ||||||
|             target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) |  | ||||||
|             command = ["python", f"{self.path}/utils/search.py", "-t", "grep_search", "-d", f"{target_dir}", "-q", f"{query}"] |  | ||||||
|             self._grep_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) |  | ||||||
|  |  | ||||||
|     def _load_grep_ui(self, data): |  | ||||||
|         Gtk.main_iteration() |  | ||||||
|  |  | ||||||
|         if not data in ("", None): |  | ||||||
|             jdata = json.loads( data ) |  | ||||||
|             jkeys = jdata.keys() |  | ||||||
|             for key in jkeys: |  | ||||||
|                 sub_keys    = jdata[key].keys() |  | ||||||
|                 grep_result = jdata[key] |  | ||||||
|  |  | ||||||
|                 widget = GrepPreviewWidget(key, sub_keys, grep_result, self.grep_query) |  | ||||||
|                 self._grep_list.add(widget) |  | ||||||
| @@ -1,137 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os |  | ||||||
| import threading |  | ||||||
| import inspect |  | ||||||
| import time |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk |  | ||||||
| from gi.repository import GLib |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from plugins.plugin_base import PluginBase |  | ||||||
| from .mixins.file_search_mixin import FileSearchMixin |  | ||||||
| from .mixins.grep_search_mixin import GrepSearchMixin |  | ||||||
| from .utils.ipc_server import IPCServer |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # 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(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): |  | ||||||
|     def __init__(self): |  | ||||||
|         super().__init__() |  | ||||||
|  |  | ||||||
|         self.path              = os.path.dirname(os.path.realpath(__file__)) |  | ||||||
|         self.name              = "Search"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus |  | ||||||
|                                            #       where self.name should not be needed for message comms |  | ||||||
|         self._GLADE_FILE       = f"{self.path}/search_dialog.glade" |  | ||||||
|  |  | ||||||
|         self._search_dialog    = None |  | ||||||
|         self._active_path      = None |  | ||||||
|         self.file_list_parent  = None |  | ||||||
|         self.grep_list_parent  = None |  | ||||||
|         self._file_list        = None |  | ||||||
|         self._grep_list        = None |  | ||||||
|         self._grep_proc        = None |  | ||||||
|         self._list_proc        = None |  | ||||||
|         self.pause_fifo_update = False |  | ||||||
|         self.update_list_ui_buffer = () |  | ||||||
|         self.grep_query        = "" |  | ||||||
|         self.search_query      = "" |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def run(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) |  | ||||||
|  |  | ||||||
|         self._search_dialog = self._builder.get_object("search_dialog") |  | ||||||
|         self.fsearch        = self._builder.get_object("fsearch") |  | ||||||
|  |  | ||||||
|         self.grep_list_parent = self._builder.get_object("grep_list_parent") |  | ||||||
|         self.file_list_parent = self._builder.get_object("file_list_parent") |  | ||||||
|  |  | ||||||
|         self._event_system.subscribe("update-file-ui", self._load_file_ui) |  | ||||||
|         self._event_system.subscribe("update-grep-ui", self._load_grep_ui) |  | ||||||
|         self._event_system.subscribe("show_search_page", self._show_page) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         self.create_ipc_listener() |  | ||||||
|  |  | ||||||
|     def generate_reference_ui_element(self): |  | ||||||
|         item = Gtk.ImageMenuItem(self.name) |  | ||||||
|         item.set_image( Gtk.Image(stock=Gtk.STOCK_FIND) ) |  | ||||||
|         item.connect("activate", self._show_page) |  | ||||||
|         item.set_always_show_image(True) |  | ||||||
|         return item |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _show_page(self, widget=None, eve=None): |  | ||||||
|         self._event_system.emit("get_current_state") |  | ||||||
|  |  | ||||||
|         state               = self._fm_state |  | ||||||
|         self._event_message = None |  | ||||||
|  |  | ||||||
|         self._active_path   = state.tab.get_current_directory() |  | ||||||
|         response            = self._search_dialog.run() |  | ||||||
|         self._search_dialog.hide() |  | ||||||
|  |  | ||||||
|     # TODO: Merge the below methods into some unified logic |  | ||||||
|     def reset_grep_box(self) -> None: |  | ||||||
|         try: |  | ||||||
|             child = self.grep_list_parent.get_children()[0] |  | ||||||
|             self._grep_list = None |  | ||||||
|             self.grep_list_parent.remove(child) |  | ||||||
|         except Exception: |  | ||||||
|             ... |  | ||||||
|  |  | ||||||
|         self._grep_list = Gtk.Box() |  | ||||||
|         self._grep_list.set_orientation(Gtk.Orientation.VERTICAL) |  | ||||||
|         self.grep_list_parent.add(self._grep_list) |  | ||||||
|         self.grep_list_parent.show_all() |  | ||||||
|  |  | ||||||
|         time.sleep(0.05) |  | ||||||
|         Gtk.main_iteration() |  | ||||||
|  |  | ||||||
|     def reset_file_list_box(self) -> None: |  | ||||||
|         try: |  | ||||||
|             child = self.file_list_parent.get_children()[0] |  | ||||||
|             self._file_list = None |  | ||||||
|             self.file_list_parent.remove(child) |  | ||||||
|         except Exception: |  | ||||||
|             ... |  | ||||||
|  |  | ||||||
|         self._file_list = Gtk.Box() |  | ||||||
|         self._file_list.set_orientation(Gtk.Orientation.VERTICAL) |  | ||||||
|         self.file_list_parent.add(self._file_list) |  | ||||||
|         self.file_list_parent.show_all() |  | ||||||
|  |  | ||||||
|         time.sleep(0.05) |  | ||||||
|         Gtk.main_iteration() |  | ||||||
| @@ -1,277 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <!-- Generated with glade 3.38.2 --> |  | ||||||
| <interface> |  | ||||||
|   <requires lib="gtk+" version="3.16"/> |  | ||||||
|   <object class="GtkDialog" id="search_dialog"> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="border-width">6</property> |  | ||||||
|     <property name="title" translatable="yes">Search...</property> |  | ||||||
|     <property name="modal">True</property> |  | ||||||
|     <property name="window-position">center-on-parent</property> |  | ||||||
|     <property name="default-width">720</property> |  | ||||||
|     <property name="default-height">620</property> |  | ||||||
|     <property name="destroy-with-parent">True</property> |  | ||||||
|     <property name="type-hint">dialog</property> |  | ||||||
|     <property name="skip-taskbar-hint">True</property> |  | ||||||
|     <property name="skip-pager-hint">True</property> |  | ||||||
|     <property name="gravity">center</property> |  | ||||||
|     <child internal-child="vbox"> |  | ||||||
|       <object class="GtkBox" id="dialog_vbox"> |  | ||||||
|         <property name="visible">True</property> |  | ||||||
|         <property name="can-focus">False</property> |  | ||||||
|         <property name="spacing">12</property> |  | ||||||
|         <child internal-child="action_area"> |  | ||||||
|           <object class="GtkButtonBox" id="dialog_action_area"> |  | ||||||
|             <property name="visible">True</property> |  | ||||||
|             <property name="can-focus">False</property> |  | ||||||
|             <property name="layout-style">end</property> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkButton" id="cancel_button"> |  | ||||||
|                 <property name="label">gtk-cancel</property> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="can-default">True</property> |  | ||||||
|                 <property name="receives-default">False</property> |  | ||||||
|                 <property name="use-stock">True</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">True</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">0</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkButton" id="ok_button"> |  | ||||||
|                 <property name="label">gtk-ok</property> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="can-default">True</property> |  | ||||||
|                 <property name="receives-default">False</property> |  | ||||||
|                 <property name="use-stock">True</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">True</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">1</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|           <packing> |  | ||||||
|             <property name="expand">False</property> |  | ||||||
|             <property name="fill">False</property> |  | ||||||
|             <property name="pack-type">end</property> |  | ||||||
|             <property name="position">0</property> |  | ||||||
|           </packing> |  | ||||||
|         </child> |  | ||||||
|         <child> |  | ||||||
|           <object class="GtkNotebook"> |  | ||||||
|             <property name="visible">True</property> |  | ||||||
|             <property name="can-focus">True</property> |  | ||||||
|             <property name="show-border">False</property> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkBox"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="orientation">vertical</property> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkBox"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkSearchEntry" id="fsearch"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="tooltip-text" translatable="yes">Query...</property> |  | ||||||
|                         <property name="primary-icon-name">edit-find-symbolic</property> |  | ||||||
|                         <property name="primary-icon-activatable">False</property> |  | ||||||
|                         <property name="primary-icon-sensitive">False</property> |  | ||||||
|                         <property name="placeholder-text" translatable="yes">Search for file...</property> |  | ||||||
|                         <signal name="search-changed" handler="_run_find_file_query" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">True</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">0</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkButton"> |  | ||||||
|                         <property name="label">gtk-stop</property> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="receives-default">True</property> |  | ||||||
|                         <property name="use-stock">True</property> |  | ||||||
|                         <signal name="released" handler="_handle_find_file_query" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">1</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">False</property> |  | ||||||
|                     <property name="fill">True</property> |  | ||||||
|                     <property name="position">0</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkScrolledWindow"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">True</property> |  | ||||||
|                     <property name="hexpand">True</property> |  | ||||||
|                     <property name="vexpand">True</property> |  | ||||||
|                     <property name="shadow-type">in</property> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkViewport"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">False</property> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkBox" id="file_list_parent"> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">False</property> |  | ||||||
|                             <property name="orientation">vertical</property> |  | ||||||
|                             <property name="spacing">5</property> |  | ||||||
|                             <property name="baseline-position">top</property> |  | ||||||
|                             <child> |  | ||||||
|                               <placeholder/> |  | ||||||
|                             </child> |  | ||||||
|                           </object> |  | ||||||
|                         </child> |  | ||||||
|                       </object> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">True</property> |  | ||||||
|                     <property name="fill">True</property> |  | ||||||
|                     <property name="position">1</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|               </object> |  | ||||||
|             </child> |  | ||||||
|             <child type="tab"> |  | ||||||
|               <object class="GtkLabel"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="label" translatable="yes">File Search</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="tab-fill">False</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkBox"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="orientation">vertical</property> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkBox"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkSearchEntry"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="tooltip-text" translatable="yes">Query...</property> |  | ||||||
|                         <property name="primary-icon-name">edit-find-symbolic</property> |  | ||||||
|                         <property name="primary-icon-activatable">False</property> |  | ||||||
|                         <property name="primary-icon-sensitive">False</property> |  | ||||||
|                         <property name="placeholder-text" translatable="yes">Query string in file...</property> |  | ||||||
|                         <signal name="search-changed" handler="_run_grep_query" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">True</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">0</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkButton"> |  | ||||||
|                         <property name="label">gtk-stop</property> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="receives-default">True</property> |  | ||||||
|                         <property name="use-stock">True</property> |  | ||||||
|                         <signal name="released" handler="_handle_grep_query" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">1</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">False</property> |  | ||||||
|                     <property name="fill">True</property> |  | ||||||
|                     <property name="position">0</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkScrolledWindow"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">True</property> |  | ||||||
|                     <property name="hexpand">True</property> |  | ||||||
|                     <property name="vexpand">True</property> |  | ||||||
|                     <property name="shadow-type">in</property> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkViewport"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">False</property> |  | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkBox" id="grep_list_parent"> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">False</property> |  | ||||||
|                             <property name="orientation">vertical</property> |  | ||||||
|                             <child> |  | ||||||
|                               <placeholder/> |  | ||||||
|                             </child> |  | ||||||
|                           </object> |  | ||||||
|                         </child> |  | ||||||
|                       </object> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">True</property> |  | ||||||
|                     <property name="fill">True</property> |  | ||||||
|                     <property name="position">1</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="position">1</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child type="tab"> |  | ||||||
|               <object class="GtkLabel"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="label" translatable="yes">Grep Search</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="position">1</property> |  | ||||||
|                 <property name="tab-fill">False</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <placeholder/> |  | ||||||
|             </child> |  | ||||||
|             <child type="tab"> |  | ||||||
|               <placeholder/> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|           <packing> |  | ||||||
|             <property name="expand">True</property> |  | ||||||
|             <property name="fill">True</property> |  | ||||||
|             <property name="position">1</property> |  | ||||||
|           </packing> |  | ||||||
|         </child> |  | ||||||
|       </object> |  | ||||||
|     </child> |  | ||||||
|     <action-widgets> |  | ||||||
|       <action-widget response="-6">cancel_button</action-widget> |  | ||||||
|       <action-widget response="-5">ok_button</action-widget> |  | ||||||
|     </action-widgets> |  | ||||||
|   </object> |  | ||||||
| </interface> |  | ||||||
| @@ -1,90 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os, threading, pickle |  | ||||||
| from multiprocessing.connection import Listener, Client |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| from gi.repository import GLib |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class IPCServer: |  | ||||||
|     """ Create a listener so that other SolarFM instances send requests back to existing instance. """ |  | ||||||
|     def __init__(self, ipc_address: str = '127.0.0.1', conn_type: str = "socket"): |  | ||||||
|         self.is_ipc_alive     = False |  | ||||||
|         self._ipc_port        = 4848 |  | ||||||
|         self._ipc_address     = ipc_address |  | ||||||
|         self._conn_type       = conn_type |  | ||||||
|         self._ipc_authkey     = b'' + bytes(f'solarfm-search_grep-ipc', 'utf-8') |  | ||||||
|         self._ipc_timeout     = 15.0 |  | ||||||
|  |  | ||||||
|         if conn_type == "socket": |  | ||||||
|             self._ipc_address = f'/tmp/solarfm-search_grep-ipc.sock' |  | ||||||
|         elif conn_type == "full_network": |  | ||||||
|             self._ipc_address = '0.0.0.0' |  | ||||||
|         elif conn_type == "full_network_unsecured": |  | ||||||
|             self._ipc_authkey = None |  | ||||||
|             self._ipc_address = '0.0.0.0' |  | ||||||
|         elif conn_type == "local_network_unsecured": |  | ||||||
|             self._ipc_authkey = None |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @daemon_threaded |  | ||||||
|     def create_ipc_listener(self) -> None: |  | ||||||
|         if self._conn_type == "socket": |  | ||||||
|             if os.path.exists(self._ipc_address): |  | ||||||
|                 os.unlink(self._ipc_address) |  | ||||||
|  |  | ||||||
|             listener = Listener(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey) |  | ||||||
|         elif "unsecured" not in self._conn_type: |  | ||||||
|             listener = Listener((self._ipc_address, self._ipc_port), authkey=self._ipc_authkey) |  | ||||||
|         else: |  | ||||||
|             listener = Listener((self._ipc_address, self._ipc_port)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         self.is_ipc_alive = True |  | ||||||
|         while True: |  | ||||||
|             conn = listener.accept() |  | ||||||
|  |  | ||||||
|             if not self.pause_fifo_update: |  | ||||||
|                 self.handle_message(conn) |  | ||||||
|             else: |  | ||||||
|                 conn.close() |  | ||||||
|  |  | ||||||
|         listener.close() |  | ||||||
|  |  | ||||||
|     def handle_message(self, conn) -> None: |  | ||||||
|         while True: |  | ||||||
|             msg  = conn.recv() |  | ||||||
|  |  | ||||||
|             if "SEARCH|" in msg: |  | ||||||
|                 file = msg.split("SEARCH|")[1].strip() |  | ||||||
|                 if file: |  | ||||||
|                     GLib.idle_add(self._load_file_ui, file, priority=GLib.PRIORITY_LOW) |  | ||||||
|  |  | ||||||
|             if "GREP|" in msg: |  | ||||||
|                 data = msg.split("GREP|")[1].strip() |  | ||||||
|                 if data: |  | ||||||
|                     GLib.idle_add(self._load_grep_ui, data, priority=GLib.PRIORITY_LOW) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|             conn.close() |  | ||||||
|             break |  | ||||||
|  |  | ||||||
|     def send_ipc_message(self, message: str = "Empty Data...") -> None: |  | ||||||
|         try: |  | ||||||
|             if self._conn_type == "socket": |  | ||||||
|                 conn = Client(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey) |  | ||||||
|             elif "unsecured" not in self._conn_type: |  | ||||||
|                 conn = Client((self._ipc_address, self._ipc_port), authkey=self._ipc_authkey) |  | ||||||
|             else: |  | ||||||
|                 conn = Client((self._ipc_address, self._ipc_port)) |  | ||||||
|  |  | ||||||
|             conn.send(message) |  | ||||||
|             conn.close() |  | ||||||
|         except ConnectionRefusedError as e: |  | ||||||
|             print("Connection refused...") |  | ||||||
|         except Exception as e: |  | ||||||
|             print(repr(e)) |  | ||||||
| @@ -1,152 +0,0 @@ | |||||||
| #!/usr/bin/python3 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Python imports |  | ||||||
| import os |  | ||||||
| import traceback |  | ||||||
| import argparse |  | ||||||
| import threading |  | ||||||
| import json |  | ||||||
| import base64 |  | ||||||
| import time |  | ||||||
| import pickle |  | ||||||
| from setproctitle import setproctitle |  | ||||||
| from multiprocessing.connection import Client |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| _ipc_address = f'/tmp/solarfm-search_grep-ipc.sock' |  | ||||||
| _ipc_authkey = b'' + bytes(f'solarfm-search_grep-ipc', 'utf-8') |  | ||||||
|  |  | ||||||
| filter = (".cpp", ".css", ".c", ".go", ".html", ".htm", ".java", ".js", ".json", ".lua", ".md", ".py", ".rs", ".toml", ".xml", ".pom") + \ |  | ||||||
|             (".txt", ".text", ".sh", ".cfg", ".conf", ".log") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # 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 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def send_ipc_message(message) -> None: |  | ||||||
|     conn = Client(address=_ipc_address, family="AF_UNIX", authkey=_ipc_authkey) |  | ||||||
|     conn.send(message) |  | ||||||
|     conn.close() |  | ||||||
|  |  | ||||||
|     # NOTE: Kinda important as this prevents overloading the UI thread |  | ||||||
|     time.sleep(0.05) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def file_search(path, query): |  | ||||||
|     try: |  | ||||||
|         for _path, _dir, _files in os.walk(path, topdown = True): |  | ||||||
|              for file in _files: |  | ||||||
|                  if query in file.lower(): |  | ||||||
|                      target = os.path.join(_path, file) |  | ||||||
|                      data = f"SEARCH|{json.dumps([target, file])}" |  | ||||||
|                      send_ipc_message(data) |  | ||||||
|     except Exception as e: |  | ||||||
|         print("Couldn't traverse to path. Might be permissions related...") |  | ||||||
|         traceback.print_exc() |  | ||||||
|  |  | ||||||
| def _search_for_string(file, query): |  | ||||||
|     b64_file = base64.urlsafe_b64encode(file.encode('utf-8')).decode('utf-8') |  | ||||||
|     grep_result_set = {} |  | ||||||
|     padding = 15 |  | ||||||
|  |  | ||||||
|     with open(file, 'rb') as fp: |  | ||||||
|         # NOTE: I know there's an issue if there's a very large file with content |  | ||||||
|         #       all on one line will lower and dupe it. And, yes, it will only |  | ||||||
|         #       return one instance from the file. |  | ||||||
|         try: |  | ||||||
|             for i, raw in enumerate(fp): |  | ||||||
|                 line   = None |  | ||||||
|                 llower = raw.lower() |  | ||||||
|                 if not query in llower: |  | ||||||
|                     continue |  | ||||||
|  |  | ||||||
|                 if len(raw) > 72: |  | ||||||
|                     start  = 0 |  | ||||||
|                     end    = len(raw) - 1 |  | ||||||
|                     index  = llower.index(query) |  | ||||||
|                     sindex = llower.index(query) - 15 if index >= 15 else abs(start - index) - index |  | ||||||
|                     eindex = sindex + 15 if end > (index + 15) else abs(index - end) + index |  | ||||||
|                     line   = raw[sindex:eindex] |  | ||||||
|                 else: |  | ||||||
|                     line = raw |  | ||||||
|  |  | ||||||
|                 b64_line = base64.urlsafe_b64encode(line).decode('utf-8') |  | ||||||
|                 if f"{b64_file}" in grep_result_set.keys(): |  | ||||||
|                     grep_result_set[f"{b64_file}"][f"{i+1}"] = b64_line |  | ||||||
|                 else: |  | ||||||
|                     grep_result_set[f"{b64_file}"] = {} |  | ||||||
|                     grep_result_set[f"{b64_file}"] = {f"{i+1}": b64_line} |  | ||||||
|  |  | ||||||
|         except Exception as e: |  | ||||||
|             ... |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             data = f"GREP|{json.dumps(grep_result_set)}" |  | ||||||
|             send_ipc_message(data) |  | ||||||
|         except Exception as e: |  | ||||||
|             ... |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @daemon_threaded |  | ||||||
| def _search_for_string_threaded(file, query): |  | ||||||
|     _search_for_string(file, query) |  | ||||||
|  |  | ||||||
| def grep_search(path, query): |  | ||||||
|     try: |  | ||||||
|         for file in os.listdir(path): |  | ||||||
|             target = os.path.join(path, file) |  | ||||||
|             if os.path.isdir(target): |  | ||||||
|                 grep_search(target, query) |  | ||||||
|             else: |  | ||||||
|                 if target.lower().endswith(filter): |  | ||||||
|                     size = os.path.getsize(target) |  | ||||||
|                     if not size > 5000: |  | ||||||
|                         _search_for_string(target, query) |  | ||||||
|                     else: |  | ||||||
|                         _search_for_string_threaded(target, query) |  | ||||||
|  |  | ||||||
|     except Exception as e: |  | ||||||
|         print("Couldn't traverse to path. Might be permissions related...") |  | ||||||
|         traceback.print_exc() |  | ||||||
|  |  | ||||||
| def search(args): |  | ||||||
|     if args.type == "file_search": |  | ||||||
|         file_search(args.dir, args.query.lower()) |  | ||||||
|  |  | ||||||
|     if args.type == "grep_search": |  | ||||||
|         grep_search(args.dir, args.query.lower().encode("utf-8")) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == "__main__": |  | ||||||
|     try: |  | ||||||
|         setproctitle('SolarFM: File Search - Grepy') |  | ||||||
|  |  | ||||||
|         parser = argparse.ArgumentParser() |  | ||||||
|         # Add long and short arguments |  | ||||||
|         parser.add_argument("--type", "-t", default=None, help="Type of search to do.") |  | ||||||
|         parser.add_argument("--dir", "-d", default=None, help="Directory root for search type.") |  | ||||||
|         parser.add_argument("--query", "-q", default=None, help="Query search is working against.") |  | ||||||
|  |  | ||||||
|         # Read arguments (If any...) |  | ||||||
|         args = parser.parse_args() |  | ||||||
|         search(args) |  | ||||||
|     except Exception as e: |  | ||||||
|         traceback.print_exc() |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Widgets Module |  | ||||||
| """ |  | ||||||
| @@ -1,16 +0,0 @@ | |||||||
| # Python imports |  | ||||||
|  |  | ||||||
| # Gtk imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FilePreviewWidget(Gtk.LinkButton): |  | ||||||
|     def __init__(self, path, file): |  | ||||||
|         super(FilePreviewWidget, self).__init__() |  | ||||||
|         self.set_label(file) |  | ||||||
|         self.set_uri(f"file://{path}") |  | ||||||
|         self.show_all() |  | ||||||
| @@ -1,60 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import base64 |  | ||||||
| import re |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class GrepPreviewWidget(Gtk.Box): |  | ||||||
|     def __init__(self, _path, sub_keys, _data, _grep_query): |  | ||||||
|         super(GrepPreviewWidget, self).__init__() |  | ||||||
|  |  | ||||||
|         self.set_orientation(Gtk.Orientation.VERTICAL) |  | ||||||
|         line_color      = "#e0cc64" |  | ||||||
|         highlight_color = "#FBF719" |  | ||||||
|         grep_query      = _grep_query |  | ||||||
|  |  | ||||||
|         path  = self.decode_str(_path) |  | ||||||
|         lbl   = '/'.join( path.split("/")[-3:] ) |  | ||||||
|         title = Gtk.LinkButton.new_with_label(uri=f"file://{path}", label=lbl) |  | ||||||
|  |  | ||||||
|         text_view = Gtk.TextView() |  | ||||||
|         buffer    = text_view.get_buffer() |  | ||||||
|         text_view.set_editable(False) |  | ||||||
|         text_view.set_monospace(True) |  | ||||||
|         text_view.set_wrap_mode(Gtk.WrapMode.NONE) |  | ||||||
|  |  | ||||||
|         for i, key in enumerate(sub_keys): |  | ||||||
|             line_num = self.make_utf8_line_num(line_color, key) |  | ||||||
|             itr      = buffer.get_end_iter() |  | ||||||
|             buffer.insert_markup(itr, line_num, len(line_num)) |  | ||||||
|  |  | ||||||
|             decoded  = f"\t{self.decode_str(_data[key])}" |  | ||||||
|             self.make_utf8_line_highlight(buffer, itr, i, highlight_color, decoded, grep_query) |  | ||||||
|  |  | ||||||
|         self.add(title) |  | ||||||
|         self.add(text_view) |  | ||||||
|         self.show_all() |  | ||||||
|  |  | ||||||
|     def decode_str(self, target): |  | ||||||
|         return base64.urlsafe_b64decode(target.encode('utf-8')).decode('utf-8') |  | ||||||
|  |  | ||||||
|     def make_utf8_line_num(self, color, target): |  | ||||||
|         return bytes(f"\n<span foreground='{color}'>{target}</span>", "utf-8").decode("utf-8") |  | ||||||
|  |  | ||||||
|     def make_utf8_line_highlight(self, buffer, itr, i, color, target, query): |  | ||||||
|         parts = re.split(r"(" + query + ")(?i)", target.replace("\n", "")) |  | ||||||
|         for part in parts: |  | ||||||
|             itr  = buffer.get_end_iter() |  | ||||||
|  |  | ||||||
|             if not query.lower() == part.lower() and not query.lower() in part.lower(): |  | ||||||
|                 buffer.insert(itr, part, length=len(part)) |  | ||||||
|             else: |  | ||||||
|                 new_s = f"<span foreground='#000000' background='{color}'>{part}</span>" |  | ||||||
|                 _part = bytes(new_s, "utf-8").decode("utf-8") |  | ||||||
|                 buffer.insert_markup(itr, _part, len(_part)) |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Module |  | ||||||
| """ |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Package |  | ||||||
| """ |  | ||||||
| @@ -1,13 +0,0 @@ | |||||||
| { |  | ||||||
|     "manifest": { |  | ||||||
|         "name": "Example Plugin", |  | ||||||
|         "author": "John Doe", |  | ||||||
|         "version": "0.0.1", |  | ||||||
|         "support": "", |  | ||||||
|         "requests": { |  | ||||||
|             "ui_target": "plugin_control_list", |  | ||||||
|             "pass_fm_events": "true", |  | ||||||
|             "bind_keys": ["Example Plugin||send_message:<Control>f"] |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,49 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os |  | ||||||
| import threading |  | ||||||
| import subprocess |  | ||||||
| import ime |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from plugins.plugin_base import PluginBase |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # 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.name               = "Example Plugin"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus |  | ||||||
|                                                     #       where self.name should not be needed for message comms |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def generate_reference_ui_element(self): |  | ||||||
|         button = Gtk.Button(label=self.name) |  | ||||||
|         button.connect("button-release-event", self.send_message) |  | ||||||
|         return button |  | ||||||
|  |  | ||||||
|     def run(self): |  | ||||||
|         ... |  | ||||||
|  |  | ||||||
|     def send_message(self, widget=None, eve=None): |  | ||||||
|         message = "Hello, World!" |  | ||||||
|         event_system.emit("display_message", ("warning", message, None)) |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Module |  | ||||||
| """ |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Package |  | ||||||
| """ |  | ||||||
| @@ -1,16 +0,0 @@ | |||||||
| { |  | ||||||
|     "manifest": { |  | ||||||
|         "name": "Trasher", |  | ||||||
|         "author": "ITDominator", |  | ||||||
|         "version": "0.0.1", |  | ||||||
|         "support": "", |  | ||||||
|         "requests": { |  | ||||||
|             "ui_target": "context_menu", |  | ||||||
|             "pass_fm_events": "true", |  | ||||||
|             "bind_keys": [ |  | ||||||
|                             "Trasher||delete_files:Delete", |  | ||||||
|                             "Trasher||trash_files:<Control>d" |  | ||||||
|                         ] |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,143 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os |  | ||||||
| import threading |  | ||||||
| import subprocess |  | ||||||
| import inspect |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk |  | ||||||
| from gi.repository import GLib |  | ||||||
| from gi.repository import 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 run(self): |  | ||||||
|         self._event_system.subscribe("show_trash_buttons", self._show_trash_buttons) |  | ||||||
|         self._event_system.subscribe("hide_trash_buttons", self._hide_trash_buttons) |  | ||||||
|         self._event_system.subscribe("delete_files", self.delete_files) |  | ||||||
|         self._event_system.subscribe("trash_files", self.trash_files) |  | ||||||
|  |  | ||||||
|     def generate_reference_ui_element(self): |  | ||||||
|         trash_a = Gtk.MenuItem("Trash Actions") |  | ||||||
|         trash_menu = Gtk.Menu() |  | ||||||
|  |  | ||||||
|         self.restore = Gtk.MenuItem("Restore From Trash") |  | ||||||
|         self.restore.connect("activate", self.restore_trash_files) |  | ||||||
|  |  | ||||||
|         self.empty = Gtk.MenuItem("Empty Trash") |  | ||||||
|         self.empty.connect("activate", self.empty_trash) |  | ||||||
|  |  | ||||||
|         trash = Gtk.ImageMenuItem("Trash") |  | ||||||
|         trash.set_image( Gtk.Image.new_from_icon_name("user-trash", 16) ) |  | ||||||
|         trash.connect("activate", self.trash_files) |  | ||||||
|         trash.set_always_show_image(True) |  | ||||||
|  |  | ||||||
|         go_to = Gtk.ImageMenuItem("Go To Trash") |  | ||||||
|         go_to.set_image( Gtk.Image.new_from_icon_name("user-trash", 16) ) |  | ||||||
|         go_to.connect("activate", self.go_to_trash) |  | ||||||
|         go_to.set_always_show_image(True) |  | ||||||
|  |  | ||||||
|         delete = Gtk.ImageMenuItem("Delete") |  | ||||||
|         delete.set_image( Gtk.Image(stock=Gtk.STOCK_DELETE) ) |  | ||||||
|         delete.connect("activate", self.delete_files) |  | ||||||
|         delete.set_always_show_image(True) |  | ||||||
|  |  | ||||||
|         trash_a.set_submenu(trash_menu) |  | ||||||
|         trash_a.show_all() |  | ||||||
|         self._appen_menu_items(trash_menu, [self.restore, self.empty, trash, go_to, delete]) |  | ||||||
|  |  | ||||||
|         return trash_a |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _appen_menu_items(self, menu, items): |  | ||||||
|         for item in items: |  | ||||||
|             menu.append(item) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _show_trash_buttons(self): |  | ||||||
|         self.restore.show() |  | ||||||
|         self.empty.show() |  | ||||||
|  |  | ||||||
|     def _hide_trash_buttons(self): |  | ||||||
|         self.restore.hide() |  | ||||||
|         self.empty.hide() |  | ||||||
|  |  | ||||||
|     def delete_files(self, widget = None, eve = None): |  | ||||||
|         self._event_system.emit("do_hide_context_menu") |  | ||||||
|         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("do_hide_context_menu") |  | ||||||
|         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("do_hide_context_menu") |  | ||||||
|         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._event_system.emit("do_hide_context_menu") |  | ||||||
|         self.trashman.empty(verbose = verbocity) |  | ||||||
|  |  | ||||||
|     def go_to_trash(self, widget = None, eve = None, verbocity = False): |  | ||||||
|         self._event_system.emit("do_hide_context_menu") |  | ||||||
|         self._event_system.emit("go_to_path", self.trash_files_path) |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Module |  | ||||||
| """ |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Package |  | ||||||
| """ |  | ||||||
| @@ -1,12 +0,0 @@ | |||||||
| { |  | ||||||
|     "manifest": { |  | ||||||
|         "name": "VOD Thumbnailer", |  | ||||||
|         "author": "ITDominator", |  | ||||||
|         "version": "0.0.1", |  | ||||||
|         "support": "", |  | ||||||
|         "requests": { |  | ||||||
|             "ui_target": "context_menu_plugins", |  | ||||||
|             "pass_fm_events": "true" |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,146 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os |  | ||||||
| import threading |  | ||||||
| import subprocess |  | ||||||
| import time |  | ||||||
| import inspect |  | ||||||
| import hashlib |  | ||||||
| from datetime import datetime |  | ||||||
|  |  | ||||||
| # Gtk imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| gi.require_version('GdkPixbuf', '2.0') |  | ||||||
| from gi.repository import Gtk |  | ||||||
| from gi.repository import GLib |  | ||||||
| from gi.repository import Gio |  | ||||||
| from gi.repository import GdkPixbuf |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from plugins.plugin_base import PluginBase |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # 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}/re_thumbnailer.glade" |  | ||||||
|         self.name                   = "VOD Thumbnailer"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus |  | ||||||
|                                                          #       where self.name should not be needed for message comms |  | ||||||
|  |  | ||||||
|         self._thumbnailer_dialog    = None |  | ||||||
|         self._thumbnail_preview_img = None |  | ||||||
|         self._scrub_step            = None |  | ||||||
|         self._file_name             = None |  | ||||||
|         self._file_location         = None |  | ||||||
|         self._file_hash             = None |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def run(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) |  | ||||||
|  |  | ||||||
|         self._thumbnailer_dialog    = self._builder.get_object("thumbnailer_dialog") |  | ||||||
|         self._scrub_step            = self._builder.get_object("scrub_step") |  | ||||||
|         self._file_name             = self._builder.get_object("file_name") |  | ||||||
|         self._file_location         = self._builder.get_object("file_location") |  | ||||||
|         self._thumbnail_preview_img = self._builder.get_object("thumbnail_preview_img") |  | ||||||
|         self._file_hash             = self._builder.get_object("file_hash") |  | ||||||
|  |  | ||||||
|     def generate_reference_ui_element(self): |  | ||||||
|         pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(f"{self.path}/../../icons/video.png", 16, 16, True) |  | ||||||
|         icon   = Gtk.Image.new_from_pixbuf(pixbuf) |  | ||||||
|         item   = Gtk.ImageMenuItem(self.name) |  | ||||||
|  |  | ||||||
|         item.set_image( icon ) |  | ||||||
|         item.connect("activate", self._show_thumbnailer_page) |  | ||||||
|         item.set_always_show_image(True) |  | ||||||
|         return item |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def _show_thumbnailer_page(self, widget=None, eve=None): |  | ||||||
|         self._event_system.emit("get_current_state") |  | ||||||
|  |  | ||||||
|         state               = self._fm_state |  | ||||||
|         self._event_message = None |  | ||||||
|  |  | ||||||
|         GLib.idle_add(self._process_changes, (state)) |  | ||||||
|  |  | ||||||
|     def _process_changes(self, state): |  | ||||||
|         self._fm_state = None |  | ||||||
|  |  | ||||||
|         if len(state.selected_files) == 1: |  | ||||||
|             if state.selected_files[0].lower().endswith(state.tab.fvideos): |  | ||||||
|                 self._fm_state = state |  | ||||||
|                 self._set_ui_data() |  | ||||||
|                 response   = self._thumbnailer_dialog.run() |  | ||||||
|                 if response in [Gtk.ResponseType.CLOSE, Gtk.ResponseType.DELETE_EVENT]: |  | ||||||
|                     self._thumbnailer_dialog.hide() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _regenerate_thumbnail(self, widget=None, eve=None): |  | ||||||
|         scrub_percent = int(self._scrub_step.get_value()) |  | ||||||
|         file          = self._file_name.get_text() |  | ||||||
|         dir           = self._file_location.get_text() |  | ||||||
|         file_hash     = self._file_hash.get_text() |  | ||||||
|         hash_img_pth  = f"{self._fm_state.tab.ABS_THUMBS_PTH}/{file_hash}.jpg" |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             os.remove(hash_img_pth) if os.path.isfile(hash_img_pth) else ... |  | ||||||
|  |  | ||||||
|             self._fm_state.tab.create_thumbnail(dir, file, f"{scrub_percent}%") |  | ||||||
|             preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(hash_img_pth) |  | ||||||
|             self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf) |  | ||||||
|  |  | ||||||
|             img_pixbuf = self._fm_state.tab.create_scaled_image(hash_img_pth) |  | ||||||
|             tree_pth   = self._fm_state.icon_grid.get_selected_items()[0] |  | ||||||
|             itr        = self._fm_state.store.get_iter(tree_pth) |  | ||||||
|             pixbuff    = self._fm_state.store.get(itr, 0)[0] |  | ||||||
|             self._fm_state.store.set(itr, 0, img_pixbuf) |  | ||||||
|         except Exception as e: |  | ||||||
|             print(repr(e)) |  | ||||||
|             print("Couldn't regenerate thumbnail!") |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _set_ui_data(self): |  | ||||||
|         uri            = self._fm_state.selected_files[0] |  | ||||||
|         path           = self._fm_state.tab.get_current_directory() |  | ||||||
|         parts          = uri.split("/") |  | ||||||
|  |  | ||||||
|         file_hash      = hashlib.sha256(str.encode(uri)).hexdigest() |  | ||||||
|         hash_img_pth   = f"{self._fm_state.tab.ABS_THUMBS_PTH}/{file_hash}.jpg" |  | ||||||
|         preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(hash_img_pth) |  | ||||||
|  |  | ||||||
|         self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf) |  | ||||||
|         self._file_name.set_text(parts[ len(parts) - 1 ]) |  | ||||||
|         self._file_location.set_text(path) |  | ||||||
|         self._file_hash.set_text(file_hash) |  | ||||||
| @@ -1,239 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <!-- Generated with glade 3.38.2 --> |  | ||||||
| <interface> |  | ||||||
|   <requires lib="gtk+" version="3.16"/> |  | ||||||
|   <object class="GtkAdjustment" id="scrub_step_adjuster"> |  | ||||||
|     <property name="lower">1</property> |  | ||||||
|     <property name="upper">100</property> |  | ||||||
|     <property name="value">65</property> |  | ||||||
|     <property name="step-increment">1</property> |  | ||||||
|     <property name="page-increment">10</property> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkDialog" id="thumbnailer_dialog"> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="border-width">6</property> |  | ||||||
|     <property name="title" translatable="yes">VOD Thumbnailer</property> |  | ||||||
|     <property name="modal">True</property> |  | ||||||
|     <property name="window-position">center-on-parent</property> |  | ||||||
|     <property name="default-width">420</property> |  | ||||||
|     <property name="destroy-with-parent">True</property> |  | ||||||
|     <property name="type-hint">dialog</property> |  | ||||||
|     <property name="skip-taskbar-hint">True</property> |  | ||||||
|     <property name="skip-pager-hint">True</property> |  | ||||||
|     <property name="gravity">center</property> |  | ||||||
|     <child internal-child="vbox"> |  | ||||||
|       <object class="GtkBox" id="dialog_vbox"> |  | ||||||
|         <property name="visible">True</property> |  | ||||||
|         <property name="can-focus">False</property> |  | ||||||
|         <property name="spacing">12</property> |  | ||||||
|         <child internal-child="action_area"> |  | ||||||
|           <object class="GtkButtonBox" id="dialog_action_area"> |  | ||||||
|             <property name="visible">True</property> |  | ||||||
|             <property name="can-focus">False</property> |  | ||||||
|             <property name="layout-style">end</property> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkButton" id="cancel_button"> |  | ||||||
|                 <property name="label">gtk-close</property> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="can-default">True</property> |  | ||||||
|                 <property name="receives-default">False</property> |  | ||||||
|                 <property name="use-stock">True</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">True</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">0</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|           <packing> |  | ||||||
|             <property name="expand">False</property> |  | ||||||
|             <property name="fill">False</property> |  | ||||||
|             <property name="pack-type">end</property> |  | ||||||
|             <property name="position">0</property> |  | ||||||
|           </packing> |  | ||||||
|         </child> |  | ||||||
|         <child> |  | ||||||
|           <object class="GtkBox"> |  | ||||||
|             <property name="visible">True</property> |  | ||||||
|             <property name="can-focus">False</property> |  | ||||||
|             <property name="orientation">vertical</property> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkImage" id="thumbnail_preview_img"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="stock">gtk-missing-image</property> |  | ||||||
|                 <property name="icon_size">6</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">True</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">0</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkBox"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="orientation">vertical</property> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkLabel"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="margin-start">5</property> |  | ||||||
|                     <property name="label" translatable="yes">New Thumbnail Scrub Step: </property> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">False</property> |  | ||||||
|                     <property name="fill">True</property> |  | ||||||
|                     <property name="position">0</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkScale" id="scrub_step"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">True</property> |  | ||||||
|                     <property name="margin-start">5</property> |  | ||||||
|                     <property name="margin-end">5</property> |  | ||||||
|                     <property name="adjustment">scrub_step_adjuster</property> |  | ||||||
|                     <property name="show-fill-level">True</property> |  | ||||||
|                     <property name="round-digits">1</property> |  | ||||||
|                     <property name="digits">0</property> |  | ||||||
|                     <property name="value-pos">right</property> |  | ||||||
|                     <signal name="value-changed" handler="_regenerate_thumbnail" swapped="no"/> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">True</property> |  | ||||||
|                     <property name="fill">True</property> |  | ||||||
|                     <property name="position">1</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">False</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">1</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkTable" id="general_table"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="border-width">4</property> |  | ||||||
|                 <property name="n-rows">3</property> |  | ||||||
|                 <property name="n-columns">2</property> |  | ||||||
|                 <property name="column-spacing">12</property> |  | ||||||
|                 <property name="row-spacing">6</property> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkLabel"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="label" translatable="yes"><b>File _Name:</b></property> |  | ||||||
|                     <property name="use-markup">True</property> |  | ||||||
|                     <property name="use-underline">True</property> |  | ||||||
|                     <property name="mnemonic-widget">file_name</property> |  | ||||||
|                     <property name="xalign">0</property> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="x-options">GTK_FILL</property> |  | ||||||
|                     <property name="y-options"/> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkEntry" id="file_name"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">True</property> |  | ||||||
|                     <property name="editable">False</property> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="left-attach">1</property> |  | ||||||
|                     <property name="right-attach">2</property> |  | ||||||
|                     <property name="y-options"/> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkLabel"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="label" translatable="yes"><b>_Location:</b></property> |  | ||||||
|                     <property name="use-markup">True</property> |  | ||||||
|                     <property name="use-underline">True</property> |  | ||||||
|                     <property name="mnemonic-widget">file_location</property> |  | ||||||
|                     <property name="xalign">0</property> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="top-attach">1</property> |  | ||||||
|                     <property name="bottom-attach">2</property> |  | ||||||
|                     <property name="x-options">GTK_FILL</property> |  | ||||||
|                     <property name="y-options"/> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkEntry" id="file_location"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">True</property> |  | ||||||
|                     <property name="editable">False</property> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="left-attach">1</property> |  | ||||||
|                     <property name="right-attach">2</property> |  | ||||||
|                     <property name="top-attach">1</property> |  | ||||||
|                     <property name="bottom-attach">2</property> |  | ||||||
|                     <property name="x-options">GTK_FILL</property> |  | ||||||
|                     <property name="y-options"/> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkLabel" id="hash"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="label" translatable="yes"><b>_Thumbnail Hash:</b></property> |  | ||||||
|                     <property name="use-markup">True</property> |  | ||||||
|                     <property name="use-underline">True</property> |  | ||||||
|                     <property name="mnemonic-widget">file_location</property> |  | ||||||
|                     <property name="xalign">0</property> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="top-attach">2</property> |  | ||||||
|                     <property name="bottom-attach">3</property> |  | ||||||
|                     <property name="x-options">GTK_FILL</property> |  | ||||||
|                     <property name="y-options"/> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkEntry" id="file_hash"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">True</property> |  | ||||||
|                     <property name="editable">False</property> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="left-attach">1</property> |  | ||||||
|                     <property name="right-attach">2</property> |  | ||||||
|                     <property name="top-attach">2</property> |  | ||||||
|                     <property name="bottom-attach">3</property> |  | ||||||
|                     <property name="x-options">GTK_FILL</property> |  | ||||||
|                     <property name="y-options"/> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">False</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">2</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|           <packing> |  | ||||||
|             <property name="expand">True</property> |  | ||||||
|             <property name="fill">True</property> |  | ||||||
|             <property name="position">1</property> |  | ||||||
|           </packing> |  | ||||||
|         </child> |  | ||||||
|       </object> |  | ||||||
|     </child> |  | ||||||
|     <action-widgets> |  | ||||||
|       <action-widget response="-7">cancel_button</action-widget> |  | ||||||
|     </action-widgets> |  | ||||||
|   </object> |  | ||||||
| </interface> |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Module |  | ||||||
| """ |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Pligin Package |  | ||||||
| """ |  | ||||||
| @@ -1,17 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
|  |  | ||||||
| # . CONFIG.sh |  | ||||||
|  |  | ||||||
| # set -o xtrace       ## To debug scripts |  | ||||||
| # set -o errexit      ## To exit on error |  | ||||||
| # set -o errunset     ## To exit if a variable is referenced but not set |  | ||||||
|  |  | ||||||
|  |  | ||||||
| function main() { |  | ||||||
|     cd "$(dirname "")" |  | ||||||
|     echo "Working Dir: " $(pwd) |  | ||||||
|  |  | ||||||
|     LINK=`xclip -selection clipboard -o` |  | ||||||
|     yt-dlp --write-sub --embed-sub --sub-langs en -o "${1}/%(title)s.%(ext)s" "${LINK}" |  | ||||||
| } |  | ||||||
| main "$@"; |  | ||||||
| @@ -1,12 +0,0 @@ | |||||||
| { |  | ||||||
|     "manifest": { |  | ||||||
|         "name": "Youtube Download", |  | ||||||
|         "author": "ITDominator", |  | ||||||
|         "version": "0.0.1", |  | ||||||
|         "support": "", |  | ||||||
|         "requests": { |  | ||||||
|             "ui_target": "plugin_control_list", |  | ||||||
|             "pass_fm_events": "true" |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,53 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os, threading, subprocess, time |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from plugins.plugin_base import PluginBase |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # 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.name              = "Youtube Download"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus |  | ||||||
|                                                      #       where self.name should not be needed for message comms |  | ||||||
|  |  | ||||||
|     def generate_reference_ui_element(self): |  | ||||||
|         button = Gtk.Button(label=self.name) |  | ||||||
|         button.connect("button-release-event", self._do_download) |  | ||||||
|         return button |  | ||||||
|  |  | ||||||
|     def run(self): |  | ||||||
|         ... |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _do_download(self, widget=None, eve=None): |  | ||||||
|         self._event_system.emit("get_current_state") |  | ||||||
|  |  | ||||||
|         dir = self._fm_state.tab.get_current_directory() |  | ||||||
|         self._download(dir) |  | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def _download(self, dir): |  | ||||||
|         subprocess.Popen([f'{self.path}/download.sh', dir]) |  | ||||||
							
								
								
									
										12
									
								
								src/debs/clear_pycache_dirs.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										12
									
								
								src/debs/clear_pycache_dirs.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | # set -o xtrace       ## To debug scripts | ||||||
|  | # set -o errexit      ## To exit on error | ||||||
|  | # set -o errunset     ## To exit if a variable is referenced but not set | ||||||
|  |  | ||||||
|  |  | ||||||
|  | function main() { | ||||||
|  |     find . -name "__pycache__" -exec rm -rf $1 {} \; | ||||||
|  |     find . -name "*.pyc" -exec rm -rf $1 {} \; | ||||||
|  | } | ||||||
|  | main | ||||||
							
								
								
									
										0
									
								
								src/debs/solarfm-0-0-1-x64/bin/solarfm
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										0
									
								
								src/debs/solarfm-0-0-1-x64/bin/solarfm
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -4,26 +4,20 @@ import builtins | |||||||
| # Lib imports | # Lib imports | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from ipc_server import IPCServer | from signal_classes.DBusControllerMixin import DBusControllerMixin | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class EventSystem(IPCServer): | class Builtins(DBusControllerMixin): | ||||||
|     """ Inheret IPCServerMixin. Create an pub/sub systems. """ |     """Docstring for __builtins__ extender""" | ||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         super(EventSystem, self).__init__() |         # NOTE: The format used is list of [type, target, data] | ||||||
|  |  | ||||||
|         # NOTE: The format used is list of [type, target, (data,)] Where: |  | ||||||
|         #             type is useful context for control flow, |  | ||||||
|         #             target is the method to call, |  | ||||||
|         #             data is the method parameters to give |  | ||||||
|         #       Where data may be any kind of data |         #       Where data may be any kind of data | ||||||
|         self._gui_events    = [] |         self._gui_events    = [] | ||||||
|         self._module_events = [] |         self._fm_events     = [] | ||||||
|  |         self.is_ipc_alive   = False | ||||||
|  |  | ||||||
|  |  | ||||||
|     # Makeshift fake "events" type system FIFO |     # Makeshift fake "events" type system FIFO | ||||||
|     def _pop_gui_event(self): |     def _pop_gui_event(self): | ||||||
| @@ -31,9 +25,9 @@ class EventSystem(IPCServer): | |||||||
|             return self._gui_events.pop(0) |             return self._gui_events.pop(0) | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|     def _pop_module_event(self): |     def _pop_fm_event(self): | ||||||
|         if len(self._module_events) > 0: |         if len(self._fm_events) > 0: | ||||||
|             return self._module_events.pop(0) |             return self._fm_events.pop(0) | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -42,33 +36,31 @@ class EventSystem(IPCServer): | |||||||
|             self._gui_events.append(event) |             self._gui_events.append(event) | ||||||
|             return None |             return None | ||||||
|  |  | ||||||
|         raise Exception("Invald event format! Please do:  [type, target, (data,)]") |         raise Exception("Invald event format! Please do:  [type, target, data]") | ||||||
|  |  | ||||||
|     def push_module_event(self, event): |     def push_fm_event(self, event): | ||||||
|         if len(event) == 3: |         if len(event) == 3: | ||||||
|             self._module_events.append(event) |             self._fm_events.append(event) | ||||||
|             return None |             return None | ||||||
|  |  | ||||||
|         raise Exception("Invald event format! Please do:  [type, target, (data,)]") |         raise Exception("Invald event format! Please do:  [type, target, data]") | ||||||
|  |  | ||||||
|     def read_gui_event(self): |     def read_gui_event(self): | ||||||
|         return self._gui_events[0] |         return self._gui_events[0] | ||||||
|  |  | ||||||
|     def read_module_event(self): |     def read_fm_event(self): | ||||||
|         return self._module_events[0] |         return self._fm_events[0] | ||||||
|  |  | ||||||
|     def consume_gui_event(self): |     def consume_gui_event(self): | ||||||
|         return self._pop_gui_event() |         return self._pop_gui_event() | ||||||
|  |  | ||||||
|     def consume_module_event(self): |     def consume_fm_event(self): | ||||||
|         return self._pop_module_event() |         return self._pop_fm_event() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # NOTE: Just reminding myself we can add to builtins two different ways... | # NOTE: Just reminding myself we can add to builtins two different ways... | ||||||
| # __builtins__.update({"event_system": Builtins()}) | # __builtins__.update({"event_system": Builtins()}) | ||||||
| builtins.app_name          = "SolarFM" | builtins.event_system      = Builtins() | ||||||
| builtins.event_system      = EventSystem() | builtins.event_sleep_time  = 0.5 | ||||||
| builtins.event_sleep_time  = 0.2 |  | ||||||
| builtins.debug             = False | builtins.debug             = False | ||||||
| builtins.trace_debug       = False |  | ||||||
|   | |||||||
| @@ -1,3 +1,51 @@ | |||||||
| """ | # Python imports | ||||||
| Base module | import os, inspect, time | ||||||
| """ |  | ||||||
|  | # Lib imports | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from utils import Settings | ||||||
|  | from signal_classes import Controller | ||||||
|  | from __builtins__ import Builtins | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Main(Builtins): | ||||||
|  |     def __init__(self, args, unknownargs): | ||||||
|  |         event_system.create_ipc_server() | ||||||
|  |         time.sleep(0.5) | ||||||
|  |         if not event_system.is_ipc_alive: | ||||||
|  |             if unknownargs: | ||||||
|  |                 for arg in unknownargs: | ||||||
|  |                     if os.path.isdir(arg): | ||||||
|  |                         message = f"FILE|{arg}" | ||||||
|  |                         event_system.send_ipc_message(message) | ||||||
|  |  | ||||||
|  |             if args.new_tab and os.path.isdir(args.new_tab): | ||||||
|  |                 message = f"FILE|{args.new_tab}" | ||||||
|  |                 event_system.send_ipc_message(message) | ||||||
|  |  | ||||||
|  |             raise Exception("IPC Server Exists: Will send path(s) to it and close...") | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         settings = Settings() | ||||||
|  |         settings.createWindow() | ||||||
|  |  | ||||||
|  |         controller = Controller(args, unknownargs, settings) | ||||||
|  |         if not controller: | ||||||
|  |             raise Exception("Controller exited and doesn't exist...") | ||||||
|  |  | ||||||
|  |         # Gets the methods from the classes and sets to handler. | ||||||
|  |         # Then, builder connects to any signals it needs. | ||||||
|  |         classes  = [controller] | ||||||
|  |         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)) | ||||||
|  |  | ||||||
|  |         settings.builder.connect_signals(handlers) | ||||||
|   | |||||||
| @@ -15,17 +15,15 @@ gi.require_version('Gtk', '3.0') | |||||||
| from gi.repository import Gtk | from gi.repository import Gtk | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from app import Application | from __init__ import Main | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|     """ Set process title, get arguments, and create GTK main thread. """ |  | ||||||
|  |  | ||||||
|     try: |     try: | ||||||
|         # import web_pdb |         # import web_pdb | ||||||
|         # web_pdb.set_trace() |         # web_pdb.set_trace() | ||||||
|  |  | ||||||
|         setproctitle('SolarFM') |         setproctitle('solarfm') | ||||||
|         faulthandler.enable()  # For better debug info |         faulthandler.enable()  # For better debug info | ||||||
|         parser = argparse.ArgumentParser() |         parser = argparse.ArgumentParser() | ||||||
|         # Add long and short arguments |         # Add long and short arguments | ||||||
| @@ -35,7 +33,7 @@ if __name__ == "__main__": | |||||||
|         # Read arguments (If any...) |         # Read arguments (If any...) | ||||||
|         args, unknownargs = parser.parse_known_args() |         args, unknownargs = parser.parse_known_args() | ||||||
|  |  | ||||||
|         Application(args, unknownargs) |         Main(args, unknownargs) | ||||||
|         Gtk.main() |         Gtk.main() | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
|         traceback.print_exc() |         traceback.print_exc() | ||||||
|   | |||||||
| @@ -1,55 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os, inspect, time |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from utils.settings import Settings |  | ||||||
| from context.controller import Controller |  | ||||||
| from __builtins__ import EventSystem |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Application(EventSystem): |  | ||||||
|     """ Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """ |  | ||||||
|  |  | ||||||
|     def __init__(self, args, unknownargs): |  | ||||||
|         if not trace_debug: |  | ||||||
|             event_system.create_ipc_server() |  | ||||||
|             time.sleep(0.1) |  | ||||||
|  |  | ||||||
|             if not event_system.is_ipc_alive: |  | ||||||
|                 if unknownargs: |  | ||||||
|                     for arg in unknownargs: |  | ||||||
|                         if os.path.isdir(arg): |  | ||||||
|                             message = f"FILE|{arg}" |  | ||||||
|                             event_system.send_ipc_message(message) |  | ||||||
|  |  | ||||||
|                 if args.new_tab and os.path.isdir(args.new_tab): |  | ||||||
|                     message = f"FILE|{args.new_tab}" |  | ||||||
|                     event_system.send_ipc_message(message) |  | ||||||
|  |  | ||||||
|                 raise Exception("IPC Server Exists: Will send path(s) to it and close...") |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         settings = Settings() |  | ||||||
|         settings.create_window() |  | ||||||
|  |  | ||||||
|         controller = Controller(args, unknownargs, settings) |  | ||||||
|         if not controller: |  | ||||||
|             raise Exception("Controller exited and doesn't exist...") |  | ||||||
|  |  | ||||||
|         # Gets the methods from the classes and sets to handler. |  | ||||||
|         # Then, builder connects to any signals it needs. |  | ||||||
|         classes  = [controller] |  | ||||||
|         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)) |  | ||||||
|  |  | ||||||
|         settings.builder.connect_signals(handlers) |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Gtk Bound Signal Module |  | ||||||
| """ |  | ||||||
| @@ -1,171 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os, gc, threading, time |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk, GLib |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from .mixins.exception_hook_mixin import ExceptionHookMixin |  | ||||||
| from .mixins.ui_mixin import UIMixin |  | ||||||
| from .signals.ipc_signals_mixin import IPCSignalsMixin |  | ||||||
| from .signals.keyboard_signals_mixin import KeyboardSignalsMixin |  | ||||||
| from .controller_data import Controller_Data |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def threaded(fn): |  | ||||||
|     def wrapper(*args, **kwargs): |  | ||||||
|         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() |  | ||||||
|     return wrapper |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMixin, Controller_Data): |  | ||||||
|     """ Controller coordinates the mixins and is somewhat the root hub of it all. """ |  | ||||||
|     def __init__(self, args, unknownargs, _settings): |  | ||||||
|         self.setup_controller_data(_settings) |  | ||||||
|         self.window.show() |  | ||||||
|  |  | ||||||
|         self.generate_windows(self.state) |  | ||||||
|         self.plugins.launch_plugins() |  | ||||||
|  |  | ||||||
|         if debug: |  | ||||||
|             self.window.set_interactive_debugging(True) |  | ||||||
|  |  | ||||||
|         if not trace_debug: |  | ||||||
|             self.gui_event_observer() |  | ||||||
|  |  | ||||||
|             if unknownargs: |  | ||||||
|                 for arg in unknownargs: |  | ||||||
|                     if os.path.isdir(arg): |  | ||||||
|                         message = f"FILE|{arg}" |  | ||||||
|                         event_system.send_ipc_message(message) |  | ||||||
|  |  | ||||||
|             if args.new_tab and os.path.isdir(args.new_tab): |  | ||||||
|                 message = f"FILE|{args.new_tab}" |  | ||||||
|                 event_system.send_ipc_message(message) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def tear_down(self, widget=None, eve=None): |  | ||||||
|         event_system.send_ipc_message("close server") |  | ||||||
|         self.fm_controller.save_state() |  | ||||||
|         time.sleep(event_sleep_time) |  | ||||||
|         Gtk.main_quit() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def gui_event_observer(self): |  | ||||||
|         while True: |  | ||||||
|             time.sleep(event_sleep_time) |  | ||||||
|             event = event_system.consume_gui_event() |  | ||||||
|             if event: |  | ||||||
|                 try: |  | ||||||
|                     type, target, data = event |  | ||||||
|                     if type: |  | ||||||
|                         method = getattr(self.__class__, "handle_gui_event_and_set_message") |  | ||||||
|                         GLib.idle_add(method, *(self, type, target, data)) |  | ||||||
|                     else: |  | ||||||
|                         method = getattr(self.__class__, target) |  | ||||||
|                         GLib.idle_add(method, *(self, *data,)) |  | ||||||
|                 except Exception as e: |  | ||||||
|                     print(repr(e)) |  | ||||||
|  |  | ||||||
|     def handle_gui_event_and_set_message(self, type, target, parameters): |  | ||||||
|         method = getattr(self.__class__, f"{target}") |  | ||||||
|         data   = method(*(self, *parameters)) |  | ||||||
|         self.plugins.send_message_to_plugin(type, data) |  | ||||||
|  |  | ||||||
|     def open_terminal(self, widget=None, eve=None): |  | ||||||
|         wid, tid = self.fm_controller.get_active_wid_and_tid() |  | ||||||
|         tab      = self.get_fm_window(wid).get_tab_by_id(tid) |  | ||||||
|         dir      = tab.get_current_directory() |  | ||||||
|         tab.execute(f"{tab.terminal_app}", dir) |  | ||||||
|  |  | ||||||
|     def save_load_session(self, action="save_session"): |  | ||||||
|         wid, tid          = self.fm_controller.get_active_wid_and_tid() |  | ||||||
|         tab               = self.get_fm_window(wid).get_tab_by_id(tid) |  | ||||||
|         save_load_dialog  = self.builder.get_object("save_load_dialog") |  | ||||||
|  |  | ||||||
|         if action == "save_session": |  | ||||||
|             self.fm_controller.save_state() |  | ||||||
|             return |  | ||||||
|         elif action == "save_session_as": |  | ||||||
|             save_load_dialog.set_action(Gtk.FileChooserAction.SAVE) |  | ||||||
|         elif action == "load_session": |  | ||||||
|             save_load_dialog.set_action(Gtk.FileChooserAction.OPEN) |  | ||||||
|         else: |  | ||||||
|             raise Exception(f"Unknown action given:  {action}") |  | ||||||
|  |  | ||||||
|         save_load_dialog.set_current_folder(tab.get_current_directory()) |  | ||||||
|         save_load_dialog.set_current_name("session.json") |  | ||||||
|         response = save_load_dialog.run() |  | ||||||
|         if response == Gtk.ResponseType.OK: |  | ||||||
|             if action == "save_session_as": |  | ||||||
|                 path = f"{save_load_dialog.get_current_folder()}/{save_load_dialog.get_current_name()}" |  | ||||||
|                 self.fm_controller.save_state(path) |  | ||||||
|             elif action == "load_session": |  | ||||||
|                 path         = f"{save_load_dialog.get_file().get_path()}" |  | ||||||
|                 session_json = self.fm_controller.load_state(path) |  | ||||||
|                 self.load_session(session_json) |  | ||||||
|         if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|         save_load_dialog.hide() |  | ||||||
|  |  | ||||||
|     def load_session(self, session_json): |  | ||||||
|         if debug: |  | ||||||
|             print(f"Session Data: {session_json}") |  | ||||||
|  |  | ||||||
|         self.ctrl_down  = False |  | ||||||
|         self.shift_down = False |  | ||||||
|         self.alt_down   = False |  | ||||||
|         for notebook in self.notebooks: |  | ||||||
|             self.clear_children(notebook) |  | ||||||
|  |  | ||||||
|         self.fm_controller.unload_tabs_and_windows() |  | ||||||
|         self.generate_windows(session_json) |  | ||||||
|         gc.collect() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def do_action_from_menu_controls(self, widget, event_button): |  | ||||||
|         action = widget.get_name() |  | ||||||
|         self.hide_context_menu() |  | ||||||
|         self.hide_new_file_menu() |  | ||||||
|         self.hide_edit_file_menu() |  | ||||||
|  |  | ||||||
|         if action == "open": |  | ||||||
|             self.open_files() |  | ||||||
|         if action == "open_with": |  | ||||||
|             self.show_appchooser_menu() |  | ||||||
|         if action == "execute": |  | ||||||
|             self.execute_files() |  | ||||||
|         if action == "execute_in_terminal": |  | ||||||
|             self.execute_files(in_terminal=True) |  | ||||||
|         if action == "rename": |  | ||||||
|             self.rename_files() |  | ||||||
|         if action == "cut": |  | ||||||
|             self.to_copy_files.clear() |  | ||||||
|             self.cut_files() |  | ||||||
|         if action == "copy": |  | ||||||
|             self.to_cut_files.clear() |  | ||||||
|             self.copy_files() |  | ||||||
|         if action == "paste": |  | ||||||
|             self.paste_files() |  | ||||||
|         if action == "archive": |  | ||||||
|             self.show_archiver_dialogue() |  | ||||||
|         if action == "delete": |  | ||||||
|             self.delete_files() |  | ||||||
|         if action == "trash": |  | ||||||
|             self.trash_files() |  | ||||||
|         if action == "go_to_trash": |  | ||||||
|             self.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": |  | ||||||
|             self.show_new_file_menu() |  | ||||||
|         if action in ["save_session", "save_session_as", "load_session"]: |  | ||||||
|             self.save_load_session(action) |  | ||||||
| @@ -1,157 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import sys, os, signal |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| from gi.repository import GLib |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from trasher.xdgtrash import XDGTrash |  | ||||||
| from shellfm.windows.controller import WindowController |  | ||||||
| from plugins.plugins import Plugins |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Controller_Data: |  | ||||||
|     """ Controller_Data contains most of the state of the app at ay given time. It also has some support methods. """ |  | ||||||
|  |  | ||||||
|     def setup_controller_data(self, _settings): |  | ||||||
|         self.trashman           = XDGTrash() |  | ||||||
|         self.fm_controller      = WindowController() |  | ||||||
|         self.plugins            = Plugins(_settings) |  | ||||||
|         self.state              = self.fm_controller.load_state() |  | ||||||
|         self.trashman.regenerate() |  | ||||||
|  |  | ||||||
|         self.settings           = _settings |  | ||||||
|         self.builder            = self.settings.get_builder() |  | ||||||
|         self.logger             = self.settings.get_logger() |  | ||||||
|  |  | ||||||
|         self.window             = self.settings.get_main_window() |  | ||||||
|         self.window1            = self.builder.get_object("window_1") |  | ||||||
|         self.window2            = self.builder.get_object("window_2") |  | ||||||
|         self.window3            = self.builder.get_object("window_3") |  | ||||||
|         self.window4            = self.builder.get_object("window_4") |  | ||||||
|         self.message_popup_widget = self.builder.get_object("message_popup_widget") |  | ||||||
|         self.message_text_view  = self.builder.get_object("message_text_view") |  | ||||||
|         self.message_buffer     = self.builder.get_object("message_buffer") |  | ||||||
|         self.arc_command_buffer = self.builder.get_object("arc_command_buffer") |  | ||||||
|  |  | ||||||
|         self.exists_file_rename_bttn = self.builder.get_object("exists_file_rename_bttn") |  | ||||||
|         self.warning_alert      = self.builder.get_object("warning_alert") |  | ||||||
|         self.edit_file_menu     = self.builder.get_object("edit_file_menu") |  | ||||||
|         self.file_exists_dialog = self.builder.get_object("file_exists_dialog") |  | ||||||
|         self.exists_file_label  = self.builder.get_object("exists_file_label") |  | ||||||
|         self.exists_file_field  = self.builder.get_object("exists_file_field") |  | ||||||
|         self.path_menu          = self.builder.get_object("path_menu") |  | ||||||
|         self.path_entry         = self.builder.get_object("path_entry") |  | ||||||
|  |  | ||||||
|         self.bottom_size_label       = self.builder.get_object("bottom_size_label") |  | ||||||
|         self.bottom_file_count_label = self.builder.get_object("bottom_file_count_label") |  | ||||||
|         self.bottom_path_label       = self.builder.get_object("bottom_path_label") |  | ||||||
|  |  | ||||||
|         self.trash_files_path        = GLib.get_user_data_dir() + "/Trash/files" |  | ||||||
|         self.trash_info_path         = GLib.get_user_data_dir() + "/Trash/info" |  | ||||||
|  |  | ||||||
|         # In compress commands: |  | ||||||
|         #    %n: First selected filename/dir to archive |  | ||||||
|         #    %N: All selected filenames/dirs to archive, or (with %O) a single filename |  | ||||||
|         #    %o: Resulting single archive file |  | ||||||
|         #    %O: Resulting archive per source file/directory (use changes %N meaning) |  | ||||||
|         # |  | ||||||
|         #  In extract commands: |  | ||||||
|         #    %x: Archive file to extract |  | ||||||
|         #    %g: Unique extraction target filename with optional subfolder |  | ||||||
|         #    %G: Unique extraction target filename, never with subfolder |  | ||||||
|         # |  | ||||||
|         #  In list commands: |  | ||||||
|         #      %x: Archive to list |  | ||||||
|         # |  | ||||||
|         #  Plus standard bash variables are accepted. |  | ||||||
|         self.arc_commands            = [ '$(which 7za || echo 7zr) a %o %N', |  | ||||||
|                                                                 'zip -r %o %N', |  | ||||||
|                                                                 'rar a -r %o %N', |  | ||||||
|                                                                 'tar -cvf %o %N', |  | ||||||
|                                                                 'tar -cvjf %o %N', |  | ||||||
|                                                                 'tar -cvzf %o %N', |  | ||||||
|                                                                 'tar -cvJf %o %N', |  | ||||||
|                                                                 'gzip -c %N > %O', |  | ||||||
|                                                                 'xz -cz %N > %O' |  | ||||||
|                                         ] |  | ||||||
|  |  | ||||||
|         self.notebooks          = [self.window1, self.window2, self.window3, self.window4] |  | ||||||
|         self.selected_files     = [] |  | ||||||
|         self.to_copy_files      = [] |  | ||||||
|         self.to_cut_files       = [] |  | ||||||
|         self.soft_update_lock   = {} |  | ||||||
|  |  | ||||||
|         self.single_click_open  = False |  | ||||||
|         self.is_pane1_hidden    = False |  | ||||||
|         self.is_pane2_hidden    = False |  | ||||||
|         self.is_pane3_hidden    = False |  | ||||||
|         self.is_pane4_hidden    = False |  | ||||||
|  |  | ||||||
|         self.override_drop_dest = None |  | ||||||
|         self.is_searching       = False |  | ||||||
|         self.search_icon_grid   = None |  | ||||||
|         self.search_tab         = None |  | ||||||
|  |  | ||||||
|         self.skip_edit          = False |  | ||||||
|         self.cancel_edit        = False |  | ||||||
|         self.ctrl_down          = False |  | ||||||
|         self.shift_down         = False |  | ||||||
|         self.alt_down           = False |  | ||||||
|  |  | ||||||
|         self.success_color      = self.settings.get_success_color() |  | ||||||
|         self.warning_color      = self.settings.get_warning_color() |  | ||||||
|         self.error_color        = self.settings.get_error_color() |  | ||||||
|  |  | ||||||
|         sys.excepthook = self.custom_except_hook |  | ||||||
|         self.window.connect("delete-event", self.tear_down) |  | ||||||
|         GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down) |  | ||||||
|  |  | ||||||
|     def get_current_state(self): |  | ||||||
|         ''' |  | ||||||
|         Returns the state info most useful for any given context and action intent. |  | ||||||
|  |  | ||||||
|                 Parameters: |  | ||||||
|                         a (obj): self |  | ||||||
|  |  | ||||||
|                 Returns: |  | ||||||
|                         wid, tid, tab, icon_grid, store |  | ||||||
|         ''' |  | ||||||
|         wid, tid     = self.fm_controller.get_active_wid_and_tid() |  | ||||||
|         tab          = self.get_fm_window(wid).get_tab_by_id(tid) |  | ||||||
|         icon_grid    = self.builder.get_object(f"{wid}|{tid}|icon_grid") |  | ||||||
|         store        = icon_grid.get_model() |  | ||||||
|         return wid, tid, tab, icon_grid, store |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def clear_console(self): |  | ||||||
|         ''' Clears the terminal screen. ''' |  | ||||||
|         os.system('cls' if os.name == 'nt' else 'clear') |  | ||||||
|  |  | ||||||
|     def call_method(self, _method_name, data = None): |  | ||||||
|         ''' |  | ||||||
|         Calls a method from scope of class. |  | ||||||
|  |  | ||||||
|                 Parameters: |  | ||||||
|                         a (obj): self |  | ||||||
|                         b (str): method name to be called |  | ||||||
|                         c (*): Data (if any) to be passed to the method. |  | ||||||
|                                 Note: It must be structured according to the given methods requirements. |  | ||||||
|  |  | ||||||
|                 Returns: |  | ||||||
|                         Return data is that which the calling method gives. |  | ||||||
|         ''' |  | ||||||
|         method_name = str(_method_name) |  | ||||||
|         method      = getattr(self, method_name, lambda data: f"No valid key passed...\nkey={method_name}\nargs={data}") |  | ||||||
|         return method(data) if data else method() |  | ||||||
|  |  | ||||||
|     def has_method(self, obj, name): |  | ||||||
|         ''' Checks if a given method exists. ''' |  | ||||||
|         return callable(getattr(obj, name, None)) |  | ||||||
|  |  | ||||||
|     def clear_children(self, widget): |  | ||||||
|         ''' Clear children of a gtk widget. ''' |  | ||||||
|         for child in widget.get_children(): |  | ||||||
|             widget.remove(child) |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
| Mixins module |  | ||||||
| """ |  | ||||||
| @@ -1,62 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import traceback, threading, time |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk, GLib |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def threaded(fn): |  | ||||||
|     def wrapper(*args, **kwargs): |  | ||||||
|         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() |  | ||||||
|     return wrapper |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ExceptionHookMixin: |  | ||||||
|     """ ExceptionHookMixin custom exception hook to reroute to a Gtk text area. """ |  | ||||||
|  |  | ||||||
|     def custom_except_hook(self, exec_type, value, _traceback): |  | ||||||
|         trace     = ''.join(traceback.format_tb(_traceback)) |  | ||||||
|         data      = f"Exec Type:  {exec_type}  <-->  Value:  {value}\n\n{trace}\n\n\n\n" |  | ||||||
|         start_itr = self.message_buffer.get_start_iter() |  | ||||||
|         self.message_buffer.place_cursor(start_itr) |  | ||||||
|         self.display_message(self.error, data) |  | ||||||
|  |  | ||||||
|     def display_message(self, type, text, seconds=None): |  | ||||||
|         self.message_buffer.insert_at_cursor(text) |  | ||||||
|         self.message_popup_widget.popup() |  | ||||||
|         if seconds: |  | ||||||
|             self.hide_message_timeout(seconds) |  | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def hide_message_timeout(self, seconds=3): |  | ||||||
|         time.sleep(seconds) |  | ||||||
|         GLib.idle_add(self.message_popup_widget.popdown) |  | ||||||
|  |  | ||||||
|     def save_debug_alerts(self, widget=None, eve=None): |  | ||||||
|         start_itr, end_itr   = self.message_buffer.get_bounds() |  | ||||||
|         save_location_prompt = Gtk.FileChooserDialog("Choose Save Folder", self.window, \ |  | ||||||
|                                                         action  = Gtk.FileChooserAction.SAVE, \ |  | ||||||
|                                                         buttons = (Gtk.STOCK_CANCEL, \ |  | ||||||
|                                                                     Gtk.ResponseType.CANCEL, \ |  | ||||||
|                                                                     Gtk.STOCK_SAVE, \ |  | ||||||
|                                                                     Gtk.ResponseType.OK)) |  | ||||||
|  |  | ||||||
|         text = self.message_buffer.get_text(start_itr, end_itr, False) |  | ||||||
|         resp = save_location_prompt.run() |  | ||||||
|         if (resp == Gtk.ResponseType.CANCEL) or (resp == Gtk.ResponseType.DELETE_EVENT): |  | ||||||
|             pass |  | ||||||
|         elif resp == Gtk.ResponseType.OK: |  | ||||||
|             target = save_location_prompt.get_filename(); |  | ||||||
|             with open(target, "w") as f: |  | ||||||
|                 f.write(text) |  | ||||||
|  |  | ||||||
|         save_location_prompt.destroy() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def set_arc_buffer_text(self, widget=None, eve=None): |  | ||||||
|         sid = widget.get_active_id() |  | ||||||
|         self.arc_command_buffer.set_text(self.arc_commands[int(sid)]) |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
| UI module |  | ||||||
| """ |  | ||||||
| @@ -1,202 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from .widget_mixin import WidgetMixin |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TabMixin(WidgetMixin): |  | ||||||
|     """docstring for TabMixin""" |  | ||||||
|  |  | ||||||
|     def create_tab(self, wid, path=None): |  | ||||||
|         notebook    = self.builder.get_object(f"window_{wid}") |  | ||||||
|         path_entry  = self.builder.get_object(f"path_entry") |  | ||||||
|         tab         = self.fm_controller.add_tab_for_window_by_nickname(f"window_{wid}") |  | ||||||
|         tab.logger  = self.logger |  | ||||||
|  |  | ||||||
|         tab.set_wid(wid) |  | ||||||
|         if path: tab.set_path(path) |  | ||||||
|  |  | ||||||
|         tab_widget    = self.create_tab_widget(tab) |  | ||||||
|         scroll, store = self.create_icon_grid_widget(tab, wid) |  | ||||||
|         # TODO: Fix global logic to make the below work too |  | ||||||
|         # scroll, store = self.create_icon_tree_widget(tab, wid) |  | ||||||
|         index         = notebook.append_page(scroll, tab_widget) |  | ||||||
|  |  | ||||||
|         self.fm_controller.set__wid_and_tid(wid, tab.get_id()) |  | ||||||
|         path_entry.set_text(tab.get_current_directory()) |  | ||||||
|         notebook.show_all() |  | ||||||
|         notebook.set_current_page(index) |  | ||||||
|  |  | ||||||
|         ctx = notebook.get_style_context() |  | ||||||
|         ctx.add_class("notebook-unselected-focus") |  | ||||||
|         notebook.set_tab_reorderable(scroll, True) |  | ||||||
|         self.load_store(tab, store) |  | ||||||
|         self.set_window_title() |  | ||||||
|         self.set_file_watcher(tab) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def close_tab(self, button, eve=None): |  | ||||||
|         notebook = button.get_parent().get_parent() |  | ||||||
|         wid      = int(notebook.get_name()[-1]) |  | ||||||
|         tid      = self.get_id_from_tab_box(button.get_parent()) |  | ||||||
|         scroll   = self.builder.get_object(f"{wid}|{tid}") |  | ||||||
|         page     = notebook.page_num(scroll) |  | ||||||
|         tab      = self.get_fm_window(wid).get_tab_by_id(tid) |  | ||||||
|         watcher  = tab.get_dir_watcher() |  | ||||||
|  |  | ||||||
|         watcher.cancel() |  | ||||||
|         self.get_fm_window(wid).delete_tab_by_id(tid) |  | ||||||
|         notebook.remove_page(page) |  | ||||||
|         self.fm_controller.save_state() |  | ||||||
|         self.set_window_title() |  | ||||||
|  |  | ||||||
|     def on_tab_reorder(self, child, page_num, new_index): |  | ||||||
|         wid, tid = page_num.get_name().split("|") |  | ||||||
|         window   = self.get_fm_window(wid) |  | ||||||
|         tab      = None |  | ||||||
|  |  | ||||||
|         for i, tab in enumerate(window.get_all_tabs()): |  | ||||||
|             if tab.get_id() == tid: |  | ||||||
|                 _tab    = window.get_tab_by_id(tid) |  | ||||||
|                 watcher = _tab.get_dir_watcher() |  | ||||||
|                 watcher.cancel() |  | ||||||
|                 window.get_all_tabs().insert(new_index, window.get_all_tabs().pop(i)) |  | ||||||
|  |  | ||||||
|         tab = window.get_tab_by_id(tid) |  | ||||||
|         self.set_file_watcher(tab) |  | ||||||
|         self.fm_controller.save_state() |  | ||||||
|  |  | ||||||
|     def on_tab_switch_update(self, notebook, content=None, index=None): |  | ||||||
|         self.selected_files.clear() |  | ||||||
|         wid, tid = content.get_children()[0].get_name().split("|") |  | ||||||
|         self.fm_controller.set__wid_and_tid(wid, tid) |  | ||||||
|         self.set_path_text(wid, tid) |  | ||||||
|         self.set_window_title() |  | ||||||
|  |  | ||||||
|     def get_id_from_tab_box(self, tab_box): |  | ||||||
|         return tab_box.get_children()[2].get_text() |  | ||||||
|  |  | ||||||
|     def get_tab_label(self, notebook, icon_grid): |  | ||||||
|         return notebook.get_tab_label(icon_grid.get_parent()).get_children()[0] |  | ||||||
|  |  | ||||||
|     def get_tab_close(self, notebook, icon_grid): |  | ||||||
|         return notebook.get_tab_label(icon_grid.get_parent()).get_children()[1] |  | ||||||
|  |  | ||||||
|     def get_tab_icon_grid_from_notebook(self, notebook): |  | ||||||
|         return notebook.get_children()[1].get_children()[0] |  | ||||||
|  |  | ||||||
|     def refresh_tab(data=None): |  | ||||||
|         wid, tid, tab, icon_grid, store = self.get_current_state() |  | ||||||
|         tab.load_directory() |  | ||||||
|         self.load_store(tab, store) |  | ||||||
|  |  | ||||||
|     def update_tab(self, tab_label, tab, store, wid, tid): |  | ||||||
|         self.load_store(tab, store) |  | ||||||
|         self.set_path_text(wid, tid) |  | ||||||
|  |  | ||||||
|         char_width = len(tab.get_end_of_path()) |  | ||||||
|         tab_label.set_width_chars(char_width) |  | ||||||
|         tab_label.set_label(tab.get_end_of_path()) |  | ||||||
|         self.set_window_title() |  | ||||||
|         self.set_file_watcher(tab) |  | ||||||
|         self.fm_controller.save_state() |  | ||||||
|  |  | ||||||
|     def do_action_from_bar_controls(self, widget, eve=None): |  | ||||||
|         action    = widget.get_name() |  | ||||||
|         wid, tid  = self.fm_controller.get_active_wid_and_tid() |  | ||||||
|         notebook  = self.builder.get_object(f"window_{wid}") |  | ||||||
|         store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") |  | ||||||
|         tab       = self.get_fm_window(wid).get_tab_by_id(tid) |  | ||||||
|  |  | ||||||
|         if action == "create_tab": |  | ||||||
|             dir = tab.get_current_directory() |  | ||||||
|             self.create_tab(wid, dir) |  | ||||||
|             self.fm_controller.save_state() |  | ||||||
|             return |  | ||||||
|         if action == "go_up": |  | ||||||
|             tab.pop_from_path() |  | ||||||
|         if action == "go_home": |  | ||||||
|             tab.set_to_home() |  | ||||||
|         if action == "refresh_tab": |  | ||||||
|             tab.load_directory() |  | ||||||
|         if action == "path_entry": |  | ||||||
|             focused_obj = self.window.get_focus() |  | ||||||
|             dir         = f"{tab.get_current_directory()}/" |  | ||||||
|             path        = widget.get_text() |  | ||||||
|  |  | ||||||
|             if isinstance(focused_obj, Gtk.Entry): |  | ||||||
|                 path_menu_buttons  = self.builder.get_object("path_menu_buttons") |  | ||||||
|                 query              = widget.get_text().replace(dir, "") |  | ||||||
|                 files              = tab.get_files() + tab.get_hidden() |  | ||||||
|  |  | ||||||
|                 self.clear_children(path_menu_buttons) |  | ||||||
|                 show_path_menu = False |  | ||||||
|                 for file, hash in files: |  | ||||||
|                     if os.path.isdir(f"{dir}{file}"): |  | ||||||
|                         if query.lower() in file.lower(): |  | ||||||
|                             button = Gtk.Button(label=file) |  | ||||||
|                             button.show() |  | ||||||
|                             button.connect("clicked", self.set_path_entry) |  | ||||||
|                             path_menu_buttons.add(button) |  | ||||||
|                             show_path_menu = True |  | ||||||
|  |  | ||||||
|                 if not show_path_menu: |  | ||||||
|                     self.path_menu.popdown() |  | ||||||
|                 else: |  | ||||||
|                     self.path_menu.popup() |  | ||||||
|                     widget.grab_focus_without_selecting() |  | ||||||
|                     widget.set_position(-1) |  | ||||||
|  |  | ||||||
|             if path.endswith(".") or path == dir: |  | ||||||
|                 return |  | ||||||
|  |  | ||||||
|             if not tab.set_path(path): |  | ||||||
|                 return |  | ||||||
|  |  | ||||||
|         self.update_tab(tab_label, tab, store, wid, tid) |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             widget.grab_focus_without_selecting() |  | ||||||
|             widget.set_position(-1) |  | ||||||
|         except Exception as e: |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|     def set_path_entry(self, button=None, eve=None): |  | ||||||
|         wid, tid, tab, icon_grid, store = self.get_current_state() |  | ||||||
|         path       = f"{tab.get_current_directory()}/{button.get_label()}" |  | ||||||
|         path_entry = self.builder.get_object("path_entry") |  | ||||||
|         path_entry.set_text(path) |  | ||||||
|         path_entry.grab_focus_without_selecting() |  | ||||||
|         path_entry.set_position(-1) |  | ||||||
|         self.path_menu.popdown() |  | ||||||
|  |  | ||||||
|     def keyboard_close_tab(self): |  | ||||||
|         wid, tid  = self.fm_controller.get_active_wid_and_tid() |  | ||||||
|         notebook  = self.builder.get_object(f"window_{wid}") |  | ||||||
|         scroll    = self.builder.get_object(f"{wid}|{tid}") |  | ||||||
|         page      = notebook.page_num(scroll) |  | ||||||
|         tab       = self.get_fm_window(wid).get_tab_by_id(tid) |  | ||||||
|         watcher   = tab.get_dir_watcher() |  | ||||||
|         watcher.cancel() |  | ||||||
|  |  | ||||||
|         self.get_fm_window(wid).delete_tab_by_id(tid) |  | ||||||
|         notebook.remove_page(page) |  | ||||||
|         self.fm_controller.save_state() |  | ||||||
|         self.set_window_title() |  | ||||||
|  |  | ||||||
|     def show_hide_hidden_files(self): |  | ||||||
|         wid, tid = self.fm_controller.get_active_wid_and_tid() |  | ||||||
|         tab      = self.get_fm_window(wid).get_tab_by_id(tid) |  | ||||||
|         tab.set_hiding_hidden(not tab.is_hiding_hidden()) |  | ||||||
|         tab.load_directory() |  | ||||||
|         self.builder.get_object("refresh_tab").released() |  | ||||||
| @@ -1,256 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import copy |  | ||||||
| from os.path import isdir, isfile |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gdk', '3.0') |  | ||||||
| from gi.repository import Gdk, Gio |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from .tab_mixin import TabMixin |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class WindowMixin(TabMixin): |  | ||||||
|     """docstring for WindowMixin""" |  | ||||||
|  |  | ||||||
|     def generate_windows(self, session_json = None): |  | ||||||
|         if session_json: |  | ||||||
|             for j, value in enumerate(session_json): |  | ||||||
|                 i = j + 1 |  | ||||||
|                 notebook_tggl_button = self.builder.get_object(f"tggl_notebook_{i}") |  | ||||||
|                 is_hidden = True if value[0]["window"]["isHidden"] == "True" else False |  | ||||||
|                 tabs      = value[0]["window"]["tabs"] |  | ||||||
|                 self.fm_controller.create_window() |  | ||||||
|                 notebook_tggl_button.set_active(True) |  | ||||||
|  |  | ||||||
|                 for tab in tabs: |  | ||||||
|                     self.create_new_tab_notebook(None, i, tab) |  | ||||||
|  |  | ||||||
|                 if is_hidden: |  | ||||||
|                     self.toggle_notebook_pane(notebook_tggl_button) |  | ||||||
|  |  | ||||||
|             try: |  | ||||||
|                 if not self.is_pane4_hidden: |  | ||||||
|                     icon_grid = self.window4.get_children()[1].get_children()[0] |  | ||||||
|                 elif not self.is_pane3_hidden: |  | ||||||
|                     icon_grid = self.window3.get_children()[1].get_children()[0] |  | ||||||
|                 elif not self.is_pane2_hidden: |  | ||||||
|                     icon_grid = self.window2.get_children()[1].get_children()[0] |  | ||||||
|                 elif not self.is_pane1_hidden: |  | ||||||
|                     icon_grid = self.window1.get_children()[1].get_children()[0] |  | ||||||
|  |  | ||||||
|                 icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) |  | ||||||
|                 icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) |  | ||||||
|             except Exception as e: |  | ||||||
|                 print("\n:  The saved session might be missing window data!  :\nLocation: ~/.config/solarfm/session.json\nFix: Back it up and delete it to reset.\n") |  | ||||||
|                 print(repr(e)) |  | ||||||
|         else: |  | ||||||
|             for j in range(0, 4): |  | ||||||
|                 i = j + 1 |  | ||||||
|                 self.fm_controller.create_window() |  | ||||||
|                 self.create_new_tab_notebook(None, i, None) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def get_fm_window(self, wid): |  | ||||||
|         return self.fm_controller.get_window_by_nickname(f"window_{wid}") |  | ||||||
|  |  | ||||||
|     def format_to_uris(self, store, wid, tid, treePaths, use_just_path=False): |  | ||||||
|         tab  = self.get_fm_window(wid).get_tab_by_id(tid) |  | ||||||
|         dir  = tab.get_current_directory() |  | ||||||
|         uris = [] |  | ||||||
|  |  | ||||||
|         for path in treePaths: |  | ||||||
|             itr   = store.get_iter(path) |  | ||||||
|             file  = store.get(itr, 1)[0] |  | ||||||
|             fpath = "" |  | ||||||
|  |  | ||||||
|             if not use_just_path: |  | ||||||
|                 fpath = f"file://{dir}/{file}" |  | ||||||
|             else: |  | ||||||
|                 fpath = f"{dir}/{file}" |  | ||||||
|  |  | ||||||
|             uris.append(fpath) |  | ||||||
|  |  | ||||||
|         return uris |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def set_bottom_labels(self, tab): |  | ||||||
|         _wid, _tid, _tab, icon_grid, store = self.get_current_state() |  | ||||||
|         selected_files       = icon_grid.get_selected_items() |  | ||||||
|         current_directory    = tab.get_current_directory() |  | ||||||
|         path_file            = Gio.File.new_for_path(current_directory) |  | ||||||
|         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_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 |  | ||||||
|         self.bottom_size_label.set_label(f"{formatted_mount_free} free / {formatted_mount_size}") |  | ||||||
|         self.bottom_path_label.set_label(tab.get_current_directory()) |  | ||||||
|         if selected_files: |  | ||||||
|             uris          = self.format_to_uris(store, _wid, _tid, selected_files, True) |  | ||||||
|             combined_size = 0 |  | ||||||
|             for uri in uris: |  | ||||||
|                 try: |  | ||||||
|                     file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::size", |  | ||||||
|                                                         flags=Gio.FileQueryInfoFlags.NONE, |  | ||||||
|                                                         cancellable=None) |  | ||||||
|                     file_size = file_info.get_size() |  | ||||||
|                     combined_size += file_size |  | ||||||
|                 except Exception as e: |  | ||||||
|                     if debug: |  | ||||||
|                         print(repr(e)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|             formatted_size = self.sizeof_fmt(combined_size) |  | ||||||
|             if tab.is_hiding_hidden(): |  | ||||||
|                 self.bottom_path_label.set_label(f" {len(uris)} / {tab.get_files_count()} ({formatted_size})") |  | ||||||
|             else: |  | ||||||
|                 self.bottom_path_label.set_label(f" {len(uris)} / {tab.get_not_hidden_count()} ({formatted_size})") |  | ||||||
|  |  | ||||||
|             return |  | ||||||
|  |  | ||||||
|         # If nothing selected |  | ||||||
|         if tab.get_hidden(): |  | ||||||
|             if tab.get_hidden_count() > 0: |  | ||||||
|                 self.bottom_file_count_label.set_label(f"{tab.get_not_hidden_count()} visible ({tab.get_hidden_count()} hidden)") |  | ||||||
|             else: |  | ||||||
|                 self.bottom_file_count_label.set_label(f"{tab.get_files_count()} items") |  | ||||||
|         else: |  | ||||||
|             self.bottom_file_count_label.set_label(f"{tab.get_files_count()} items") |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def set_window_title(self): |  | ||||||
|         wid, tid = self.fm_controller.get_active_wid_and_tid() |  | ||||||
|         notebook = self.builder.get_object(f"window_{wid}") |  | ||||||
|         tab      = self.get_fm_window(wid).get_tab_by_id(tid) |  | ||||||
|         dir      = tab.get_current_directory() |  | ||||||
|  |  | ||||||
|         for _notebook in self.notebooks: |  | ||||||
|             ctx = _notebook.get_style_context() |  | ||||||
|             ctx.remove_class("notebook-selected-focus") |  | ||||||
|             ctx.add_class("notebook-unselected-focus") |  | ||||||
|  |  | ||||||
|         ctx = notebook.get_style_context() |  | ||||||
|         ctx.remove_class("notebook-unselected-focus") |  | ||||||
|         ctx.add_class("notebook-selected-focus") |  | ||||||
|  |  | ||||||
|         self.window.set_title(f"SolarFM ~ {dir}") |  | ||||||
|         self.set_bottom_labels(tab) |  | ||||||
|  |  | ||||||
|     def set_path_text(self, wid, tid): |  | ||||||
|         path_entry = self.builder.get_object("path_entry") |  | ||||||
|         tab        = self.get_fm_window(wid).get_tab_by_id(tid) |  | ||||||
|         path_entry.set_text(tab.get_current_directory()) |  | ||||||
|  |  | ||||||
|     def grid_set_selected_items(self, icons_grid): |  | ||||||
|         self.selected_files = icons_grid.get_selected_items() |  | ||||||
|  |  | ||||||
|     def grid_cursor_toggled(self, icons_grid): |  | ||||||
|         print("wat...") |  | ||||||
|  |  | ||||||
|     def grid_icon_single_click(self, icons_grid, eve): |  | ||||||
|         try: |  | ||||||
|             self.path_menu.popdown() |  | ||||||
|             wid, tid = icons_grid.get_name().split("|") |  | ||||||
|             self.fm_controller.set__wid_and_tid(wid, tid) |  | ||||||
|             self.set_path_text(wid, tid) |  | ||||||
|             self.set_window_title() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|             if eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 1:   # l-click |  | ||||||
|                 if self.single_click_open: # FIXME: need to find a way to pass the model index |  | ||||||
|                     self.grid_icon_double_click(icons_grid) |  | ||||||
|             elif eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click |  | ||||||
|                 self.show_context_menu() |  | ||||||
|  |  | ||||||
|         except Exception as e: |  | ||||||
|             print(repr(e)) |  | ||||||
|             self.display_message(self.error_color, f"{repr(e)}") |  | ||||||
|  |  | ||||||
|     def grid_icon_double_click(self, icons_grid, item, data=None): |  | ||||||
|         try: |  | ||||||
|             if self.ctrl_down and self.shift_down: |  | ||||||
|                 self.unset_keys_and_data() |  | ||||||
|                 self.execute_files(in_terminal=True) |  | ||||||
|                 return |  | ||||||
|             elif self.ctrl_down: |  | ||||||
|                 self.unset_keys_and_data() |  | ||||||
|                 self.execute_files() |  | ||||||
|                 return |  | ||||||
|  |  | ||||||
|  |  | ||||||
|             wid, tid, tab, _icons_grid, store = self.get_current_state() |  | ||||||
|             notebook   = self.builder.get_object(f"window_{wid}") |  | ||||||
|             tab_label  = self.get_tab_label(notebook, icons_grid) |  | ||||||
|  |  | ||||||
|             fileName   = store[item][1] |  | ||||||
|             dir        = tab.get_current_directory() |  | ||||||
|             file       = f"{dir}/{fileName}" |  | ||||||
|  |  | ||||||
|             if isdir(file): |  | ||||||
|                 tab.set_path(file) |  | ||||||
|                 self.update_tab(tab_label, tab, store, wid, tid) |  | ||||||
|             else: |  | ||||||
|                 self.open_files() |  | ||||||
|         except Exception as e: |  | ||||||
|             self.display_message(self.error_color, f"{repr(e)}") |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def grid_on_drag_set(self, icons_grid, drag_context, data, info, time): |  | ||||||
|         action    = icons_grid.get_name() |  | ||||||
|         wid, tid  = action.split("|") |  | ||||||
|         store     = icons_grid.get_model() |  | ||||||
|         treePaths = icons_grid.get_selected_items() |  | ||||||
|         # NOTE: Need URIs as URI format for DnD to work. Will strip 'file://' |  | ||||||
|         # further down call chain when doing internal fm stuff. |  | ||||||
|         uris      = self.format_to_uris(store, wid, tid, treePaths) |  | ||||||
|         uris_text = '\n'.join(uris) |  | ||||||
|  |  | ||||||
|         data.set_uris(uris) |  | ||||||
|         data.set_text(uris_text, -1) |  | ||||||
|  |  | ||||||
|     def grid_on_drag_motion(self, icons_grid, drag_context, x, y, data): |  | ||||||
|         current   = '|'.join(self.fm_controller.get_active_wid_and_tid()) |  | ||||||
|         target    = icons_grid.get_name() |  | ||||||
|         wid, tid  = target.split("|") |  | ||||||
|         store     = icons_grid.get_model() |  | ||||||
|         treePath  = icons_grid.get_drag_dest_item().path |  | ||||||
|  |  | ||||||
|         if treePath: |  | ||||||
|             uri = self.format_to_uris(store, wid, tid, treePath)[0].replace("file://", "") |  | ||||||
|             self.override_drop_dest = uri if isdir(uri) else None |  | ||||||
|  |  | ||||||
|         if target not in current: |  | ||||||
|             self.fm_controller.set__wid_and_tid(wid, tid) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time): |  | ||||||
|         if info == 80: |  | ||||||
|             wid, tid  = self.fm_controller.get_active_wid_and_tid() |  | ||||||
|             notebook  = self.builder.get_object(f"window_{wid}") |  | ||||||
|             store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") |  | ||||||
|             tab       = self.get_fm_window(wid).get_tab_by_id(tid) |  | ||||||
|  |  | ||||||
|             uris = data.get_uris() |  | ||||||
|             dest = f"{tab.get_current_directory()}" if not self.override_drop_dest else self.override_drop_dest |  | ||||||
|             if len(uris) == 0: |  | ||||||
|                 uris = data.get_text().split("\n") |  | ||||||
|  |  | ||||||
|             from_uri = '/'.join(uris[0].replace("file://", "").split("/")[:-1]) |  | ||||||
|             if from_uri != dest: |  | ||||||
|                 self.move_files(uris, dest) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def create_new_tab_notebook(self, widget=None, wid=None, path=None): |  | ||||||
|         self.create_tab(wid, path) |  | ||||||
| @@ -1,14 +0,0 @@ | |||||||
| # Python imports |  | ||||||
|  |  | ||||||
| # Gtk imports |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from .show_hide_mixin import ShowHideMixin |  | ||||||
| from .ui.widget_file_action_mixin import WidgetFileActionMixin |  | ||||||
| from .ui.pane_mixin import PaneMixin |  | ||||||
| from .ui.window_mixin import WindowMixin |  | ||||||
| from .show_hide_mixin import ShowHideMixin |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class UIMixin(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin): |  | ||||||
|     pass |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
| Signals module |  | ||||||
| """ |  | ||||||
| @@ -1,29 +0,0 @@ | |||||||
| # Python imports |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class IPCSignalsMixin: |  | ||||||
|     """ IPCSignalsMixin handle messages from another starting solarfm process. """ |  | ||||||
|  |  | ||||||
|     def print_to_console(self, message=None): |  | ||||||
|         print(self) |  | ||||||
|         print(message) |  | ||||||
|  |  | ||||||
|     def handle_file_from_ipc(self, path): |  | ||||||
|         wid, tid   = self.fm_controller.get_active_wid_and_tid() |  | ||||||
|         notebook   = self.builder.get_object(f"window_{wid}") |  | ||||||
|         if notebook.is_visible(): |  | ||||||
|             self.create_tab(wid, path) |  | ||||||
|             return |  | ||||||
|  |  | ||||||
|         if not self.is_pane4_hidden: |  | ||||||
|             self.create_tab(4, path) |  | ||||||
|         elif not self.is_pane3_hidden: |  | ||||||
|             self.create_tab(3, path) |  | ||||||
|         elif not self.is_pane2_hidden: |  | ||||||
|             self.create_tab(2, path) |  | ||||||
|         elif not self.is_pane1_hidden: |  | ||||||
|             self.create_tab(1, path) |  | ||||||
| @@ -1,114 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import re |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| gi.require_version('Gdk', '3.0') |  | ||||||
| from gi.repository import Gtk, Gdk |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
| valid_keyvalue_pat    = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class KeyboardSignalsMixin: |  | ||||||
|     """ KeyboardSignalsMixin keyboard hooks controller. """ |  | ||||||
|  |  | ||||||
|     def unset_keys_and_data(self, widget=None, eve=None): |  | ||||||
|         self.ctrl_down    = False |  | ||||||
|         self.shift_down   = False |  | ||||||
|         self.alt_down     = False |  | ||||||
|         self.is_searching = False |  | ||||||
|  |  | ||||||
|     def global_key_press_controller(self, eve, user_data): |  | ||||||
|         keyname = Gdk.keyval_name(user_data.keyval).lower() |  | ||||||
|         if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]: |  | ||||||
|             if "control" in keyname: |  | ||||||
|                 self.ctrl_down    = True |  | ||||||
|             if "shift" in keyname: |  | ||||||
|                 self.shift_down   = True |  | ||||||
|             if "alt" in keyname: |  | ||||||
|                 self.alt_down     = True |  | ||||||
|  |  | ||||||
|     # NOTE: Yes, this should actually be mapped to some key controller setting |  | ||||||
|     #       file or something. Sue me. |  | ||||||
|     def global_key_release_controller(self, eve, user_data): |  | ||||||
|         keyname = Gdk.keyval_name(user_data.keyval).lower() |  | ||||||
|         if debug: |  | ||||||
|             print(f"global_key_release_controller > key > {keyname}") |  | ||||||
|  |  | ||||||
|         if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]: |  | ||||||
|             if "control" in keyname: |  | ||||||
|                 self.ctrl_down    = False |  | ||||||
|             if "shift" in keyname: |  | ||||||
|                 self.shift_down   = False |  | ||||||
|             if "alt" in keyname: |  | ||||||
|                 self.alt_down     = False |  | ||||||
|  |  | ||||||
|         if self.ctrl_down and self.shift_down and keyname == "t": |  | ||||||
|             self.unset_keys_and_data() |  | ||||||
|             self.trash_files() |  | ||||||
|  |  | ||||||
|         if self.ctrl_down: |  | ||||||
|             if keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]: |  | ||||||
|                 self.builder.get_object(f"tggl_notebook_{keyname.strip('kp_')}").released() |  | ||||||
|             if keyname == "q": |  | ||||||
|                 self.tear_down() |  | ||||||
|             if keyname == "slash" or keyname == "home": |  | ||||||
|                 self.builder.get_object("go_home").released() |  | ||||||
|             if keyname == "r" or keyname == "f5": |  | ||||||
|                 self.builder.get_object("refresh_tab").released() |  | ||||||
|             if keyname == "up" or keyname == "u": |  | ||||||
|                 self.builder.get_object("go_up").released() |  | ||||||
|             if keyname == "l": |  | ||||||
|                 self.unset_keys_and_data() |  | ||||||
|                 self.builder.get_object("path_entry").grab_focus() |  | ||||||
|             if keyname == "t": |  | ||||||
|                 self.builder.get_object("create_tab").released() |  | ||||||
|             if keyname == "o": |  | ||||||
|                 self.unset_keys_and_data() |  | ||||||
|                 self.open_files() |  | ||||||
|             if keyname == "w": |  | ||||||
|                 self.keyboard_close_tab() |  | ||||||
|             if keyname == "h": |  | ||||||
|                 self.show_hide_hidden_files() |  | ||||||
|             if keyname == "e": |  | ||||||
|                 self.unset_keys_and_data() |  | ||||||
|                 self.rename_files() |  | ||||||
|             if keyname == "c": |  | ||||||
|                 self.copy_files() |  | ||||||
|                 self.to_cut_files.clear() |  | ||||||
|             if keyname == "x": |  | ||||||
|                 self.to_copy_files.clear() |  | ||||||
|                 self.cut_files() |  | ||||||
|             if keyname == "v": |  | ||||||
|                 self.paste_files() |  | ||||||
|             if keyname == "n": |  | ||||||
|                 self.unset_keys_and_data() |  | ||||||
|                 self.show_new_file_menu() |  | ||||||
|  |  | ||||||
|         if keyname == "delete": |  | ||||||
|             self.unset_keys_and_data() |  | ||||||
|             self.delete_files() |  | ||||||
|         if keyname == "f2": |  | ||||||
|             self.unset_keys_and_data() |  | ||||||
|             self.rename_files() |  | ||||||
|         if keyname == "f4": |  | ||||||
|             self.unset_keys_and_data() |  | ||||||
|             self.open_terminal() |  | ||||||
|         if keyname in ["alt_l", "alt_r"]: |  | ||||||
|             top_main_menubar = self.builder.get_object("top_main_menubar") |  | ||||||
|             top_main_menubar.hide() if top_main_menubar.is_visible() else top_main_menubar.show() |  | ||||||
|  |  | ||||||
|         if re.fullmatch(valid_keyvalue_pat, keyname): |  | ||||||
|             if not self.is_searching and not self.ctrl_down \ |  | ||||||
|                 and not self.shift_down and not self.alt_down: |  | ||||||
|                     focused_obj = self.window.get_focus() |  | ||||||
|                     if isinstance(focused_obj, Gtk.IconView): |  | ||||||
|                         self.is_searching = True |  | ||||||
|                         wid, tid, self.search_tab, self.search_icon_grid, store = self.get_current_state() |  | ||||||
|                         self.unset_keys_and_data() |  | ||||||
|                         self.popup_search_files(wid, keyname) |  | ||||||
|                         return |  | ||||||
| @@ -1,90 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os, threading, time |  | ||||||
| from multiprocessing.connection import Listener, Client |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def threaded(fn): |  | ||||||
|     def wrapper(*args, **kwargs): |  | ||||||
|         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() |  | ||||||
|     return wrapper |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class IPCServer: |  | ||||||
|     """ Create a listener so that other SolarFM instances send requests back to existing instance. """ |  | ||||||
|     def __init__(self, conn_type="socket"): |  | ||||||
|         self.is_ipc_alive   = False |  | ||||||
|         self._conn_type     = conn_type |  | ||||||
|         self.ipc_authkey    = b'solarfm-ipc' |  | ||||||
|         self.ipc_timeout    = 15.0 |  | ||||||
|  |  | ||||||
|         if conn_type == "socket": |  | ||||||
|             self.ipc_address    = '/tmp/solarfm-ipc.sock' |  | ||||||
|         else: |  | ||||||
|             self.ipc_address    = '127.0.0.1' |  | ||||||
|             self.ipc_port       = 4848 |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def create_ipc_server(self): |  | ||||||
|         if self._conn_type == "socket": |  | ||||||
|             if os.path.exists(self.ipc_address): |  | ||||||
|                 return |  | ||||||
|  |  | ||||||
|             listener = Listener(address=self.ipc_address, family="AF_UNIX", authkey=self.ipc_authkey) |  | ||||||
|         else: |  | ||||||
|             listener = Listener((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         self.is_ipc_alive = True |  | ||||||
|         while True: |  | ||||||
|             conn       = listener.accept() |  | ||||||
|             start_time = time.time() |  | ||||||
|  |  | ||||||
|             print(f"New Connection: {listener.last_accepted}") |  | ||||||
|             while True: |  | ||||||
|                 msg = conn.recv() |  | ||||||
|                 if debug: |  | ||||||
|                     print(msg) |  | ||||||
|  |  | ||||||
|                 if "FILE|" in msg: |  | ||||||
|                     file = msg.split("FILE|")[1].strip() |  | ||||||
|                     if file: |  | ||||||
|                         event_system.push_gui_event([None, "handle_file_from_ipc", (file,)]) |  | ||||||
|  |  | ||||||
|                     conn.close() |  | ||||||
|                     break |  | ||||||
|  |  | ||||||
|  |  | ||||||
|                 if msg == 'close connection': |  | ||||||
|                     conn.close() |  | ||||||
|                     break |  | ||||||
|                 if msg == 'close server': |  | ||||||
|                     conn.close() |  | ||||||
|                     break |  | ||||||
|  |  | ||||||
|                 # NOTE: Not perfect but insures we don't lock up the connection for too long. |  | ||||||
|                 end_time = time.time() |  | ||||||
|                 if (end - start) > self.ipc_timeout: |  | ||||||
|                     conn.close() |  | ||||||
|  |  | ||||||
|         listener.close() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def send_ipc_message(self, message="Empty Data..."): |  | ||||||
|         try: |  | ||||||
|             if self._conn_type == "socket": |  | ||||||
|                 conn = Client(address=self.ipc_address, family="AF_UNIX", authkey=self.ipc_authkey) |  | ||||||
|             else: |  | ||||||
|                 conn = Client((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|             conn.send(message) |  | ||||||
|             conn.send('close connection') |  | ||||||
|         except Exception as e: |  | ||||||
|             print(repr(e)) |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
|     Gtk Bound Plugins Module |  | ||||||
| """ |  | ||||||
| @@ -1,83 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os, sys, importlib, traceback |  | ||||||
| from os.path import join, isdir |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk, Gio |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Plugin: |  | ||||||
|     name          = None |  | ||||||
|     module        = None |  | ||||||
|     reference     = None |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Plugins: |  | ||||||
|     """Plugins controller""" |  | ||||||
|  |  | ||||||
|     def __init__(self, settings): |  | ||||||
|         self._settings            = settings |  | ||||||
|         self._builder             = self._settings.get_builder() |  | ||||||
|         self._plugins_path        = self._settings.get_plugins_path() |  | ||||||
|         self._plugins_dir_watcher = None |  | ||||||
|         self._plugin_collection   = [] |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def launch_plugins(self): |  | ||||||
|         self._set_plugins_watcher() |  | ||||||
|         self.load_plugins() |  | ||||||
|  |  | ||||||
|     def _set_plugins_watcher(self): |  | ||||||
|         self._plugins_dir_watcher  = Gio.File.new_for_path(self._plugins_path) \ |  | ||||||
|                                             .monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable()) |  | ||||||
|         self._plugins_dir_watcher.connect("changed", self._on_plugins_changed, ()) |  | ||||||
|  |  | ||||||
|     def _on_plugins_changed(self, file_monitor, file, other_file=None, eve_type=None, data=None): |  | ||||||
|         if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED, |  | ||||||
|                         Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN, |  | ||||||
|                                                     Gio.FileMonitorEvent.MOVED_OUT]: |  | ||||||
|             self.reload_plugins(file) |  | ||||||
|  |  | ||||||
|     # @threaded |  | ||||||
|     def load_plugins(self, file=None): |  | ||||||
|         print(f"Loading plugins...") |  | ||||||
|         parent_path = os.getcwd() |  | ||||||
|  |  | ||||||
|         for file in os.listdir(self._plugins_path): |  | ||||||
|             try: |  | ||||||
|                 path = join(self._plugins_path, file) |  | ||||||
|                 if isdir(path): |  | ||||||
|                     os.chdir(path) |  | ||||||
|  |  | ||||||
|                     sys.path.insert(0, path) |  | ||||||
|                     spec = importlib.util.spec_from_file_location(file, join(path, "__main__.py")) |  | ||||||
|                     app  = importlib.util.module_from_spec(spec) |  | ||||||
|                     spec.loader.exec_module(app) |  | ||||||
|  |  | ||||||
|                     plugin_reference = app.Plugin(self._builder, event_system) |  | ||||||
|                     plugin           = Plugin() |  | ||||||
|                     plugin.name      = plugin_reference.get_plugin_name() |  | ||||||
|                     plugin.module    = path |  | ||||||
|                     plugin.reference = plugin_reference |  | ||||||
|  |  | ||||||
|                     self._plugin_collection.append(plugin) |  | ||||||
|             except Exception as e: |  | ||||||
|                 print("Malformed plugin! Not loading!") |  | ||||||
|                 traceback.print_exc() |  | ||||||
|  |  | ||||||
|         os.chdir(parent_path) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def reload_plugins(self, file=None): |  | ||||||
|         print(f"Reloading plugins... stub.") |  | ||||||
|  |  | ||||||
|     def send_message_to_plugin(self, type, data): |  | ||||||
|         print("Trying to send message to plugin...") |  | ||||||
|         for plugin in self._plugin_collection: |  | ||||||
|             if type in plugin.name: |  | ||||||
|                 print('Found plugin; posting message...') |  | ||||||
|                 plugin.reference.set_message(data) |  | ||||||
| @@ -1,3 +1 @@ | |||||||
| """ | from .windows import WindowController | ||||||
| Root of ShellFM |  | ||||||
| """ |  | ||||||
|   | |||||||
| @@ -0,0 +1,66 @@ | |||||||
|  | # Python imports | ||||||
|  | from random import randint | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from .view import View | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Window: | ||||||
|  |     def __init__(self): | ||||||
|  |         self.id_length = 10 | ||||||
|  |         self.id        = "" | ||||||
|  |         self.name      = "" | ||||||
|  |         self.nickname  = "" | ||||||
|  |         self.isHidden  = False | ||||||
|  |         self.views     = [] | ||||||
|  |  | ||||||
|  |         self.generate_id() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def random_with_N_digits(self, n): | ||||||
|  |         range_start = 10**(n-1) | ||||||
|  |         range_end = (10**n)-1 | ||||||
|  |         return randint(range_start, range_end) | ||||||
|  |  | ||||||
|  |     def generate_id(self): | ||||||
|  |         self.id = str(self.random_with_N_digits(self.id_length)) | ||||||
|  |  | ||||||
|  |     def get_window_id(self): | ||||||
|  |         return self.id | ||||||
|  |  | ||||||
|  |     def create_view(self): | ||||||
|  |         view = View() | ||||||
|  |         self.views.append(view) | ||||||
|  |         return view | ||||||
|  |  | ||||||
|  |     def pop_view(self): | ||||||
|  |         self.views.pop() | ||||||
|  |  | ||||||
|  |     def delete_view_by_id(self, vid): | ||||||
|  |         for view in self.views: | ||||||
|  |             if view.id == vid: | ||||||
|  |                 self.views.remove(view) | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def get_view_by_id(self, vid): | ||||||
|  |         for view in self.views: | ||||||
|  |             if view.id == vid: | ||||||
|  |                 return view | ||||||
|  |  | ||||||
|  |     def get_view_by_index(self, index): | ||||||
|  |         return self.views[index] | ||||||
|  |  | ||||||
|  |     def get_views_count(self): | ||||||
|  |         return len(self.views) | ||||||
|  |  | ||||||
|  |     def get_all_views(self): | ||||||
|  |         return self.views | ||||||
|  |  | ||||||
|  |     def list_files_from_views(self): | ||||||
|  |         for view in self.views: | ||||||
|  |             print(view.files) | ||||||
| @@ -0,0 +1,179 @@ | |||||||
|  | # Python imports | ||||||
|  | import threading, subprocess, time, json | ||||||
|  | from os import path | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from . import Window | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class WindowController: | ||||||
|  |     def __init__(self): | ||||||
|  |         USER_HOME              = path.expanduser('~') | ||||||
|  |         CONFIG_PATH            = USER_HOME   + "/.config/solarfm" | ||||||
|  |         self.session_file      = CONFIG_PATH + "/session.json" | ||||||
|  |  | ||||||
|  |         self._event_sleep_time = 1 | ||||||
|  |         self.active_window_id  = "" | ||||||
|  |         self.active_tab_id     = "" | ||||||
|  |         self.windows           = [] | ||||||
|  |         self.fm_event_observer() | ||||||
|  |  | ||||||
|  |     @threaded | ||||||
|  |     def fm_event_observer(self): | ||||||
|  |         while True: | ||||||
|  |             time.sleep(event_sleep_time) | ||||||
|  |             event = event_system.consume_fm_event() | ||||||
|  |             if event: | ||||||
|  |                 print(event) | ||||||
|  |  | ||||||
|  |     def set_active_data(self, wid, tid): | ||||||
|  |         self.active_window_id = str(wid) | ||||||
|  |         self.active_tab_id    = str(tid) | ||||||
|  |  | ||||||
|  |     def get_active_data(self): | ||||||
|  |         return self.active_window_id, self.active_tab_id | ||||||
|  |  | ||||||
|  |     def create_window(self): | ||||||
|  |         window          = Window() | ||||||
|  |         window.name     = "window_" + window.id | ||||||
|  |         window.nickname = "window_" + str(len(self.windows) + 1) | ||||||
|  |  | ||||||
|  |         self.windows.append(window) | ||||||
|  |         return window | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def add_view_for_window(self, win_id): | ||||||
|  |         for window in self.windows: | ||||||
|  |             if window.id == win_id: | ||||||
|  |                 return window.create_view() | ||||||
|  |  | ||||||
|  |     def add_view_for_window_by_name(self, name): | ||||||
|  |         for window in self.windows: | ||||||
|  |             if window.name == name: | ||||||
|  |                 return window.create_view() | ||||||
|  |  | ||||||
|  |     def add_view_for_window_by_nickname(self, nickname): | ||||||
|  |         for window in self.windows: | ||||||
|  |             if window.nickname == nickname: | ||||||
|  |                 return window.create_view() | ||||||
|  |  | ||||||
|  |     def pop_window(self): | ||||||
|  |         self.windows.pop() | ||||||
|  |  | ||||||
|  |     def delete_window_by_id(self, win_id): | ||||||
|  |         for window in self.windows: | ||||||
|  |             if window.id == win_id: | ||||||
|  |                 self.windows.remove(window) | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |     def delete_window_by_name(self, name): | ||||||
|  |         for window in self.windows: | ||||||
|  |             if window.name == name: | ||||||
|  |                 self.windows.remove(window) | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |     def delete_window_by_nickname(self, nickname): | ||||||
|  |         for window in self.windows: | ||||||
|  |             if window.nickname == nickname: | ||||||
|  |                 self.windows.remove(window) | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |     def get_window_by_id(self, win_id): | ||||||
|  |         for window in self.windows: | ||||||
|  |             if window.id == win_id: | ||||||
|  |                 return window | ||||||
|  |  | ||||||
|  |         raise(f"No Window by ID {win_id} found!") | ||||||
|  |  | ||||||
|  |     def get_window_by_name(self, name): | ||||||
|  |         for window in self.windows: | ||||||
|  |             if window.name == name: | ||||||
|  |                 return window | ||||||
|  |  | ||||||
|  |         raise(f"No Window by Name {name} found!") | ||||||
|  |  | ||||||
|  |     def get_window_by_nickname(self, nickname): | ||||||
|  |         for window in self.windows: | ||||||
|  |             if window.nickname == nickname: | ||||||
|  |                 return window | ||||||
|  |  | ||||||
|  |         raise(f"No Window by Nickname {nickname} found!") | ||||||
|  |  | ||||||
|  |     def get_window_by_index(self, index): | ||||||
|  |         return self.windows[index] | ||||||
|  |  | ||||||
|  |     def get_all_windows(self): | ||||||
|  |         return self.windows | ||||||
|  |  | ||||||
|  |     def set_window_nickname(self, win_id = None, nickname = ""): | ||||||
|  |         for window in self.windows: | ||||||
|  |             if window.id == win_id: | ||||||
|  |                 window.nickname = nickname | ||||||
|  |  | ||||||
|  |     def list_windows(self): | ||||||
|  |         print("\n[  ----  Windows  ----  ]\n") | ||||||
|  |         for window in self.windows: | ||||||
|  |             print(f"\nID: {window.id}") | ||||||
|  |             print(f"Name: {window.name}") | ||||||
|  |             print(f"Nickname: {window.nickname}") | ||||||
|  |             print(f"Is Hidden: {window.isHidden}") | ||||||
|  |             print(f"View Count: {window.get_views_count()}") | ||||||
|  |         print("\n-------------------------\n") | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def list_files_from_views_of_window(self, win_id): | ||||||
|  |         for window in self.windows: | ||||||
|  |             if window.id == win_id: | ||||||
|  |                 window.list_files_from_views() | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |     def get_views_count(self, win_id): | ||||||
|  |         for window in self.windows: | ||||||
|  |             if window.id == win_id: | ||||||
|  |                 return window.get_views_count() | ||||||
|  |  | ||||||
|  |     def get_views_from_window(self, win_id): | ||||||
|  |         for window in self.windows: | ||||||
|  |             if window.id == win_id: | ||||||
|  |                 return window.get_all_views() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def save_state(self): | ||||||
|  |         windows = [] | ||||||
|  |         for window in self.windows: | ||||||
|  |             views = [] | ||||||
|  |             for view in window.views: | ||||||
|  |                 views.append(view.get_current_directory()) | ||||||
|  |  | ||||||
|  |             windows.append( | ||||||
|  |                 [ | ||||||
|  |                     { | ||||||
|  |                         'window':{ | ||||||
|  |                             "ID": window.id, | ||||||
|  |                             "Name": window.name, | ||||||
|  |                             "Nickname": window.nickname, | ||||||
|  |                             "isHidden": f"{window.isHidden}", | ||||||
|  |                             'views': views | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 ] | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         with open(self.session_file, 'w') as outfile: | ||||||
|  |             json.dump(windows, outfile, separators=(',', ':'), indent=4) | ||||||
|  |  | ||||||
|  |     def load_state(self): | ||||||
|  |         if path.isfile(self.session_file): | ||||||
|  |             with open(self.session_file) as infile: | ||||||
|  |                 return json.load(infile) | ||||||
| @@ -1,3 +1,2 @@ | |||||||
| """ | from .Window import Window | ||||||
| Window module | from .WindowController import WindowController | ||||||
| """ |  | ||||||
|   | |||||||
| @@ -1,185 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import threading, json |  | ||||||
| from os import path |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from .window import Window |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def threaded(fn): |  | ||||||
|     def wrapper(*args, **kwargs): |  | ||||||
|         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() |  | ||||||
|     return wrapper |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class WindowController: |  | ||||||
|     def __init__(self): |  | ||||||
|         USER_HOME               = path.expanduser('~') |  | ||||||
|         CONFIG_PATH             = USER_HOME   + "/.config/solarfm" |  | ||||||
|         self._session_file      = CONFIG_PATH + "/session.json" |  | ||||||
|  |  | ||||||
|         self._event_sleep_time  = 1 |  | ||||||
|         self._active_window_id  = "" |  | ||||||
|         self._active_tab_id     = "" |  | ||||||
|         self._windows           = [] |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def set__wid_and_tid(self, wid, tid): |  | ||||||
|         self._active_window_id = str(wid) |  | ||||||
|         self._active_tab_id    = str(tid) |  | ||||||
|  |  | ||||||
|     def get_active_wid_and_tid(self): |  | ||||||
|         return self._active_window_id, self._active_tab_id |  | ||||||
|  |  | ||||||
|     def create_window(self): |  | ||||||
|         window = Window() |  | ||||||
|         window.set_nickname(f"window_{str(len(self._windows) + 1)}") |  | ||||||
|         self._windows.append(window) |  | ||||||
|         return window |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def add_tab_for_window(self, win_id): |  | ||||||
|         for window in self._windows: |  | ||||||
|             if window.get_id() == win_id: |  | ||||||
|                 return window.create_tab() |  | ||||||
|  |  | ||||||
|     def add_tab_for_window_by_name(self, name): |  | ||||||
|         for window in self._windows: |  | ||||||
|             if window.get_name() == name: |  | ||||||
|                 return window.create_tab() |  | ||||||
|  |  | ||||||
|     def add_tab_for_window_by_nickname(self, nickname): |  | ||||||
|         for window in self._windows: |  | ||||||
|             if window.get_nickname() == nickname: |  | ||||||
|                 return window.create_tab() |  | ||||||
|  |  | ||||||
|     def pop_window(self): |  | ||||||
|         self._windows.pop() |  | ||||||
|  |  | ||||||
|     def delete_window_by_id(self, win_id): |  | ||||||
|         for window in self._windows: |  | ||||||
|             if window.get_id() == win_id: |  | ||||||
|                 self._windows.remove(window) |  | ||||||
|                 break |  | ||||||
|  |  | ||||||
|     def delete_window_by_name(self, name): |  | ||||||
|         for window in self._windows: |  | ||||||
|             if window.get_name() == name: |  | ||||||
|                 self._windows.remove(window) |  | ||||||
|                 break |  | ||||||
|  |  | ||||||
|     def delete_window_by_nickname(self, nickname): |  | ||||||
|         for window in self._windows: |  | ||||||
|             if window.get_nickname() == nickname: |  | ||||||
|                 self._windows.remove(window) |  | ||||||
|                 break |  | ||||||
|  |  | ||||||
|     def get_window_by_id(self, win_id): |  | ||||||
|         for window in self._windows: |  | ||||||
|             if window.get_id() == win_id: |  | ||||||
|                 return window |  | ||||||
|  |  | ||||||
|         raise(f"No Window by ID {win_id} found!") |  | ||||||
|  |  | ||||||
|     def get_window_by_name(self, name): |  | ||||||
|         for window in self._windows: |  | ||||||
|             if window.get_name() == name: |  | ||||||
|                 return window |  | ||||||
|  |  | ||||||
|         raise(f"No Window by Name {name} found!") |  | ||||||
|  |  | ||||||
|     def get_window_by_nickname(self, nickname): |  | ||||||
|         for window in self._windows: |  | ||||||
|             if window.get_nickname() == nickname: |  | ||||||
|                 return window |  | ||||||
|  |  | ||||||
|         raise(f"No Window by Nickname {nickname} found!") |  | ||||||
|  |  | ||||||
|     def get_window_by_index(self, index): |  | ||||||
|         return self._windows[index] |  | ||||||
|  |  | ||||||
|     def get_all_windows(self): |  | ||||||
|         return self._windows |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def set_window_nickname(self, win_id = None, nickname = ""): |  | ||||||
|         for window in self._windows: |  | ||||||
|             if window.get_id() == win_id: |  | ||||||
|                 window.set_nickname(nickname) |  | ||||||
|  |  | ||||||
|     def list_windows(self): |  | ||||||
|         print("\n[  ----  Windows  ----  ]\n") |  | ||||||
|         for window in self._windows: |  | ||||||
|             print(f"\nID: {window.get_id()}") |  | ||||||
|             print(f"Name: {window.get_name()}") |  | ||||||
|             print(f"Nickname: {window.get_nickname()}") |  | ||||||
|             print(f"Is Hidden: {window.is_hidden()}") |  | ||||||
|             print(f"Tab Count: {window.get_tabs_count()}") |  | ||||||
|         print("\n-------------------------\n") |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def list_files_from_tabs_of_window(self, win_id): |  | ||||||
|         for window in self._windows: |  | ||||||
|             if window.get_id() == win_id: |  | ||||||
|                 window.list_files_from_tabs() |  | ||||||
|                 break |  | ||||||
|  |  | ||||||
|     def get_tabs_count(self, win_id): |  | ||||||
|         for window in self._windows: |  | ||||||
|             if window.get_id() == win_id: |  | ||||||
|                 return window.get_tabs_count() |  | ||||||
|  |  | ||||||
|     def get_tabs_from_window(self, win_id): |  | ||||||
|         for window in self._windows: |  | ||||||
|             if window.get_id() == win_id: |  | ||||||
|                 return window.get_all_tabs() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def unload_tabs_and_windows(self): |  | ||||||
|         for window in self._windows: |  | ||||||
|             window.get_all_tabs().clear() |  | ||||||
|  |  | ||||||
|         self._windows.clear() |  | ||||||
|  |  | ||||||
|     def save_state(self, session_file = None): |  | ||||||
|         if not session_file: |  | ||||||
|             session_file = self._session_file |  | ||||||
|  |  | ||||||
|         if len(self._windows) > 0: |  | ||||||
|             windows = [] |  | ||||||
|             for window in self._windows: |  | ||||||
|                 tabs = [] |  | ||||||
|                 for tab in window.get_all_tabs(): |  | ||||||
|                     tabs.append(tab.get_current_directory()) |  | ||||||
|  |  | ||||||
|                 windows.append( |  | ||||||
|                     [ |  | ||||||
|                         { |  | ||||||
|                             'window':{ |  | ||||||
|                                 "ID": window.get_id(), |  | ||||||
|                                 "Name": window.get_name(), |  | ||||||
|                                 "Nickname": window.get_nickname(), |  | ||||||
|                                 "isHidden": f"{window.is_hidden()}", |  | ||||||
|                                 'tabs': tabs |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     ] |  | ||||||
|                 ) |  | ||||||
|  |  | ||||||
|             with open(session_file, 'w') as outfile: |  | ||||||
|                 json.dump(windows, outfile, separators=(',', ':'), indent=4) |  | ||||||
|         else: |  | ||||||
|             raise Exception("Window data corrupted! Can not save session!") |  | ||||||
|  |  | ||||||
|     def load_state(self, session_file = None): |  | ||||||
|         if not session_file: |  | ||||||
|             session_file = self._session_file |  | ||||||
|  |  | ||||||
|         if path.isfile(session_file): |  | ||||||
|             with open(session_file) as infile: |  | ||||||
|                 return json.load(infile) |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
| Tabs module |  | ||||||
| """ |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
| Icons module |  | ||||||
| """ |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
| Icons mixins module |  | ||||||
| """ |  | ||||||
| @@ -1,246 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import hashlib, re |  | ||||||
| from os import listdir |  | ||||||
| from os.path import isdir, isfile, join |  | ||||||
|  |  | ||||||
| from random import randint |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from .utils.settings import Settings |  | ||||||
| from .utils.launcher import Launcher |  | ||||||
| from .utils.filehandler import FileHandler |  | ||||||
|  |  | ||||||
| from .icons.icon import Icon |  | ||||||
| from .path import Path |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Tab(Settings, FileHandler, Launcher, Icon, Path): |  | ||||||
|     def __init__(self): |  | ||||||
|         self.logger      = None |  | ||||||
|         self._id_length   = 10 |  | ||||||
|  |  | ||||||
|         self._id          = "" |  | ||||||
|         self._wid         = None |  | ||||||
|         self._dir_watcher = None |  | ||||||
|         self._hide_hidden = self.HIDE_HIDDEN_FILES |  | ||||||
|         self._files       = [] |  | ||||||
|         self._dirs        = [] |  | ||||||
|         self._vids        = [] |  | ||||||
|         self._images      = [] |  | ||||||
|         self._desktop     = [] |  | ||||||
|         self._ungrouped   = [] |  | ||||||
|         self._hidden      = [] |  | ||||||
|  |  | ||||||
|         self._generate_id() |  | ||||||
|         self.set_to_home() |  | ||||||
|  |  | ||||||
|     def load_directory(self): |  | ||||||
|         path            = self.get_path() |  | ||||||
|         self._dirs      = [] |  | ||||||
|         self._vids      = [] |  | ||||||
|         self._images    = [] |  | ||||||
|         self._desktop   = [] |  | ||||||
|         self._ungrouped = [] |  | ||||||
|         self._hidden    = [] |  | ||||||
|         self._files     = [] |  | ||||||
|  |  | ||||||
|         if not isdir(path): |  | ||||||
|             self.set_to_home() |  | ||||||
|             return "" |  | ||||||
|  |  | ||||||
|         for f in listdir(path): |  | ||||||
|             file = join(path, f) |  | ||||||
|             if self._hide_hidden: |  | ||||||
|                 if f.startswith('.'): |  | ||||||
|                     self._hidden.append(f) |  | ||||||
|                     continue |  | ||||||
|  |  | ||||||
|             if isfile(file): |  | ||||||
|                 lowerName = file.lower() |  | ||||||
|                 if lowerName.endswith(self.fvideos): |  | ||||||
|                     self._vids.append(f) |  | ||||||
|                 elif lowerName.endswith(self.fimages): |  | ||||||
|                     self._images.append(f) |  | ||||||
|                 elif lowerName.endswith((".desktop",)): |  | ||||||
|                     self._desktop.append(f) |  | ||||||
|                 else: |  | ||||||
|                     self._ungrouped.append(f) |  | ||||||
|             else: |  | ||||||
|                 self._dirs.append(f) |  | ||||||
|  |  | ||||||
|         self._dirs.sort(key=self._natural_keys) |  | ||||||
|         self._vids.sort(key=self._natural_keys) |  | ||||||
|         self._images.sort(key=self._natural_keys) |  | ||||||
|         self._desktop.sort(key=self._natural_keys) |  | ||||||
|         self._ungrouped.sort(key=self._natural_keys) |  | ||||||
|  |  | ||||||
|         self._files = self._dirs + self._vids + self._images + self._desktop + self._ungrouped |  | ||||||
|  |  | ||||||
|     def is_folder_locked(self, hash): |  | ||||||
|         if self.lock_folder: |  | ||||||
|             path_parts = self.get_path().split('/') |  | ||||||
|             file       = self.get_path_part_from_hash(hash) |  | ||||||
|  |  | ||||||
|             # Insure chilren folders are locked too. |  | ||||||
|             lockedFolderInPath = False |  | ||||||
|             for folder in self.locked_folders: |  | ||||||
|                 if folder in path_parts: |  | ||||||
|                     lockedFolderInPath = True |  | ||||||
|                     break |  | ||||||
|  |  | ||||||
|             return (file in self.locked_folders or lockedFolderInPath) |  | ||||||
|         else: |  | ||||||
|             return False |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def get_not_hidden_count(self): |  | ||||||
|         return len(self._files)    + \ |  | ||||||
|                 len(self._dirs)    + \ |  | ||||||
|                 len(self._vids)    + \ |  | ||||||
|                 len(self._images)  + \ |  | ||||||
|                 len(self._desktop) + \ |  | ||||||
|                 len(self._ungrouped) |  | ||||||
|  |  | ||||||
|     def get_hidden_count(self): |  | ||||||
|         return len(self._hidden) |  | ||||||
|  |  | ||||||
|     def get_files_count(self): |  | ||||||
|         return len(self._files) |  | ||||||
|  |  | ||||||
|     def get_path_part_from_hash(self, hash): |  | ||||||
|         files = self.get_files() |  | ||||||
|         file  = None |  | ||||||
|  |  | ||||||
|         for f in files: |  | ||||||
|             if hash == f[1]: |  | ||||||
|                 file = f[0] |  | ||||||
|                 break |  | ||||||
|  |  | ||||||
|         return file |  | ||||||
|  |  | ||||||
|     def get_files_formatted(self): |  | ||||||
|         files     = self._hash_set(self._files), |  | ||||||
|         dirs      = self._hash_set(self._dirs), |  | ||||||
|         videos    = self.get_videos(), |  | ||||||
|         images    = self._hash_set(self._images), |  | ||||||
|         desktops  = self._hash_set(self._desktop), |  | ||||||
|         ungrouped = self._hash_set(self._ungrouped) |  | ||||||
|         hidden    = self._hash_set(self._hidden) |  | ||||||
|  |  | ||||||
|         return { |  | ||||||
|             'path_head': self.get_path(), |  | ||||||
|             'list': { |  | ||||||
|                 'files': files, |  | ||||||
|                 'dirs': dirs, |  | ||||||
|                 'videos': videos, |  | ||||||
|                 'images': images, |  | ||||||
|                 'desktops': desktops, |  | ||||||
|                 'ungrouped': ungrouped, |  | ||||||
|                 'hidden': hidden |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|     def get_pixbuf_icon_str_combo(self): |  | ||||||
|         data = [] |  | ||||||
|         dir  = self.get_current_directory() |  | ||||||
|         for file in self._files: |  | ||||||
|             icon = self.create_icon(dir, file).get_pixbuf() |  | ||||||
|             data.append([icon, file]) |  | ||||||
|  |  | ||||||
|         return data |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def get_gtk_icon_str_combo(self): |  | ||||||
|         data = [] |  | ||||||
|         dir  = self.get_current_directory() |  | ||||||
|         for file in self._files: |  | ||||||
|             icon = self.create_icon(dir, file) |  | ||||||
|             data.append([icon, file[0]]) |  | ||||||
|  |  | ||||||
|         return data |  | ||||||
|  |  | ||||||
|     def get_current_directory(self): |  | ||||||
|         return self.get_path() |  | ||||||
|  |  | ||||||
|     def get_current_sub_path(self): |  | ||||||
|         path = self.get_path() |  | ||||||
|         home = f"{self.get_home()}/" |  | ||||||
|         return path.replace(home, "") |  | ||||||
|  |  | ||||||
|     def get_end_of_path(self): |  | ||||||
|         parts = self.get_current_directory().split("/") |  | ||||||
|         size  = len(parts) |  | ||||||
|         return parts[size - 1] |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def set_hiding_hidden(self, state): |  | ||||||
|         self._hide_hidden = state |  | ||||||
|  |  | ||||||
|     def is_hiding_hidden(self): |  | ||||||
|         return self._hide_hidden |  | ||||||
|  |  | ||||||
|     def get_dot_dots(self): |  | ||||||
|         return self._hash_set(['.', '..']) |  | ||||||
|  |  | ||||||
|     def get_files(self): |  | ||||||
|         return self._hash_set(self._files) |  | ||||||
|  |  | ||||||
|     def get_dirs(self): |  | ||||||
|         return self._hash_set(self._dirs) |  | ||||||
|  |  | ||||||
|     def get_videos(self): |  | ||||||
|         return self._hash_set(self._vids) |  | ||||||
|  |  | ||||||
|     def get_images(self): |  | ||||||
|         return self._hash_set(self._images) |  | ||||||
|  |  | ||||||
|     def get_desktops(self): |  | ||||||
|         return self._hash_set(self._desktop) |  | ||||||
|  |  | ||||||
|     def get_ungrouped(self): |  | ||||||
|         return self._hash_set(self._ungrouped) |  | ||||||
|  |  | ||||||
|     def get_hidden(self): |  | ||||||
|         return self._hash_set(self._hidden) |  | ||||||
|  |  | ||||||
|     def get_id(self): |  | ||||||
|         return self._id |  | ||||||
|  |  | ||||||
|     def set_wid(self, _wid): |  | ||||||
|         self._wid = _wid |  | ||||||
|  |  | ||||||
|     def get_wid(self): |  | ||||||
|         return self._wid |  | ||||||
|  |  | ||||||
|     def set_dir_watcher(self, watcher): |  | ||||||
|         self._dir_watcher = watcher |  | ||||||
|  |  | ||||||
|     def get_dir_watcher(self): |  | ||||||
|         return self._dir_watcher |  | ||||||
|  |  | ||||||
|     def _atoi(self, text): |  | ||||||
|         return int(text) if text.isdigit() else text |  | ||||||
|  |  | ||||||
|     def _natural_keys(self, text): |  | ||||||
|         return [ self._atoi(c) for c in re.split('(\d+)',text) ] |  | ||||||
|  |  | ||||||
|     def _hash_text(self, text): |  | ||||||
|         return hashlib.sha256(str.encode(text)).hexdigest()[:18] |  | ||||||
|  |  | ||||||
|     def _hash_set(self, arry): |  | ||||||
|         data = [] |  | ||||||
|         for arr in arry: |  | ||||||
|             data.append([arr, self._hash_text(arr)]) |  | ||||||
|         return data |  | ||||||
|  |  | ||||||
|     def _random_with_N_digits(self, n): |  | ||||||
|         range_start = 10**(n-1) |  | ||||||
|         range_end = (10**n)-1 |  | ||||||
|         return randint(range_start, range_end) |  | ||||||
|  |  | ||||||
|     def _generate_id(self): |  | ||||||
|         self._id = str(self._random_with_N_digits(self._id_length)) |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
| Utils module |  | ||||||
| """ |  | ||||||
| @@ -11,7 +11,7 @@ class Path: | |||||||
|         return os.path.expanduser("~") + self.subpath |         return os.path.expanduser("~") + self.subpath | ||||||
| 
 | 
 | ||||||
|     def get_path(self): |     def get_path(self): | ||||||
|         return f"/{'/'.join(self.path)}" if self.path else f"/{''.join(self.path)}" |         return "/" + "/".join(self.path) | ||||||
| 
 | 
 | ||||||
|     def get_path_list(self): |     def get_path_list(self): | ||||||
|         return self.path |         return self.path | ||||||
| @@ -21,7 +21,7 @@ class Path: | |||||||
|         self.load_directory() |         self.load_directory() | ||||||
| 
 | 
 | ||||||
|     def pop_from_path(self): |     def pop_from_path(self): | ||||||
|         try: |         if len(self.path) > 1: | ||||||
|             self.path.pop() |             self.path.pop() | ||||||
| 
 | 
 | ||||||
|             if not self.go_past_home: |             if not self.go_past_home: | ||||||
| @@ -29,8 +29,6 @@ class Path: | |||||||
|                     self.set_to_home() |                     self.set_to_home() | ||||||
| 
 | 
 | ||||||
|             self.load_directory() |             self.load_directory() | ||||||
|         except Exception as e: |  | ||||||
|             pass |  | ||||||
| 
 | 
 | ||||||
|     def set_path(self, path): |     def set_path(self, path): | ||||||
|         if path == self.get_path(): |         if path == self.get_path(): | ||||||
| @@ -0,0 +1,229 @@ | |||||||
|  | # Python imports | ||||||
|  | import hashlib | ||||||
|  | import os | ||||||
|  | from os import listdir | ||||||
|  | from os.path import isdir, isfile, join | ||||||
|  |  | ||||||
|  | from random import randint | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from .utils import Settings, Launcher, FileHandler | ||||||
|  | from .icons import Icon | ||||||
|  | from . import Path | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class View(Settings, FileHandler, Launcher, Icon, Path): | ||||||
|  |     def __init__(self): | ||||||
|  |         self. logger     = None | ||||||
|  |         self.id_length   = 10 | ||||||
|  |  | ||||||
|  |         self.id          = "" | ||||||
|  |         self.wid         = None | ||||||
|  |         self.dir_watcher = None | ||||||
|  |         self.hide_hidden = self.HIDE_HIDDEN_FILES | ||||||
|  |         self.files       = [] | ||||||
|  |         self.dirs        = [] | ||||||
|  |         self.vids        = [] | ||||||
|  |         self.images      = [] | ||||||
|  |         self.desktop     = [] | ||||||
|  |         self.ungrouped   = [] | ||||||
|  |         self.hidden      = [] | ||||||
|  |  | ||||||
|  |         self.generate_id() | ||||||
|  |         self.set_to_home() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def random_with_N_digits(self, n): | ||||||
|  |         range_start = 10**(n-1) | ||||||
|  |         range_end = (10**n)-1 | ||||||
|  |         return randint(range_start, range_end) | ||||||
|  |  | ||||||
|  |     def generate_id(self): | ||||||
|  |         self.id = str(self.random_with_N_digits(self.id_length)) | ||||||
|  |  | ||||||
|  |     def get_tab_id(self): | ||||||
|  |         return self.id | ||||||
|  |  | ||||||
|  |     def set_wid(self, _wid): | ||||||
|  |         self.wid = _wid | ||||||
|  |  | ||||||
|  |     def get_wid(self): | ||||||
|  |         return self.wid | ||||||
|  |  | ||||||
|  |     def set_dir_watcher(self, watcher): | ||||||
|  |         self.dir_watcher = watcher | ||||||
|  |  | ||||||
|  |     def get_dir_watcher(self): | ||||||
|  |         return self.dir_watcher | ||||||
|  |  | ||||||
|  |     def load_directory(self): | ||||||
|  |         path           = self.get_path() | ||||||
|  |         self.dirs      = [] | ||||||
|  |         self.vids      = [] | ||||||
|  |         self.images    = [] | ||||||
|  |         self.desktop   = [] | ||||||
|  |         self.ungrouped = [] | ||||||
|  |         self.hidden    = [] | ||||||
|  |         self.files     = [] | ||||||
|  |  | ||||||
|  |         if not isdir(path): | ||||||
|  |             self.set_to_home() | ||||||
|  |             return "" | ||||||
|  |  | ||||||
|  |         for f in listdir(path): | ||||||
|  |             file = join(path, f) | ||||||
|  |             if self.hide_hidden: | ||||||
|  |                 if f.startswith('.'): | ||||||
|  |                     self.hidden.append(f) | ||||||
|  |                     continue | ||||||
|  |  | ||||||
|  |             if isfile(file): | ||||||
|  |                 lowerName = file.lower() | ||||||
|  |                 if lowerName.endswith(self.fvideos): | ||||||
|  |                     self.vids.append(f) | ||||||
|  |                 elif lowerName.endswith(self.fimages): | ||||||
|  |                     self.images.append(f) | ||||||
|  |                 elif lowerName.endswith((".desktop",)): | ||||||
|  |                     self.desktop.append(f) | ||||||
|  |                 else: | ||||||
|  |                     self.ungrouped.append(f) | ||||||
|  |             else: | ||||||
|  |                 self.dirs.append(f) | ||||||
|  |  | ||||||
|  |         self.dirs.sort() | ||||||
|  |         self.vids.sort() | ||||||
|  |         self.images.sort() | ||||||
|  |         self.desktop.sort() | ||||||
|  |         self.ungrouped.sort() | ||||||
|  |  | ||||||
|  |         self.files = self.dirs + self.vids + self.images + self.desktop + self.ungrouped | ||||||
|  |  | ||||||
|  |     def hash_text(self, text): | ||||||
|  |         return hashlib.sha256(str.encode(text)).hexdigest()[:18] | ||||||
|  |  | ||||||
|  |     def hash_set(self, arry): | ||||||
|  |         data = [] | ||||||
|  |         for arr in arry: | ||||||
|  |             data.append([arr, self.hash_text(arr)]) | ||||||
|  |         return data | ||||||
|  |  | ||||||
|  |     def is_folder_locked(self, hash): | ||||||
|  |         if self.lock_folder: | ||||||
|  |             path_parts = self.get_path().split('/') | ||||||
|  |             file       = self.get_path_part_from_hash(hash) | ||||||
|  |  | ||||||
|  |             # Insure chilren folders are locked too. | ||||||
|  |             lockedFolderInPath = False | ||||||
|  |             for folder in self.locked_folders: | ||||||
|  |                 if folder in path_parts: | ||||||
|  |                     lockedFolderInPath = True | ||||||
|  |                     break | ||||||
|  |  | ||||||
|  |             return (file in self.locked_folders or lockedFolderInPath) | ||||||
|  |         else: | ||||||
|  |             return False | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def get_not_hidden_count(self): | ||||||
|  |         return len(self.files)    + \ | ||||||
|  |                 len(self.dirs)    + \ | ||||||
|  |                 len(self.vids)    + \ | ||||||
|  |                 len(self.images)  + \ | ||||||
|  |                 len(self.desktop) + \ | ||||||
|  |                 len(self.ungrouped) | ||||||
|  |  | ||||||
|  |     def get_hidden_count(self): | ||||||
|  |         return len(self.hidden) | ||||||
|  |  | ||||||
|  |     def get_files_count(self): | ||||||
|  |         return len(self.files) | ||||||
|  |  | ||||||
|  |     def get_path_part_from_hash(self, hash): | ||||||
|  |         files = self.get_files() | ||||||
|  |         file  = None | ||||||
|  |  | ||||||
|  |         for f in files: | ||||||
|  |             if hash == f[1]: | ||||||
|  |                 file = f[0] | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |         return file | ||||||
|  |  | ||||||
|  |     def get_files_formatted(self): | ||||||
|  |         files     = self.hash_set(self.files), | ||||||
|  |         dirs      = self.hash_set(self.dirs), | ||||||
|  |         videos    = self.get_videos(), | ||||||
|  |         images    = self.hash_set(self.images), | ||||||
|  |         desktops  = self.hash_set(self.desktop), | ||||||
|  |         ungrouped = self.hash_set(self.ungrouped) | ||||||
|  |         hidden    = self.hash_set(self.hidden) | ||||||
|  |  | ||||||
|  |         return { | ||||||
|  |             'path_head': self.get_path(), | ||||||
|  |             'list': { | ||||||
|  |                 'files': files, | ||||||
|  |                 'dirs': dirs, | ||||||
|  |                 'videos': videos, | ||||||
|  |                 'images': images, | ||||||
|  |                 'desktops': desktops, | ||||||
|  |                 'ungrouped': ungrouped, | ||||||
|  |                 'hidden': hidden | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     def get_pixbuf_icon_str_combo(self): | ||||||
|  |         data = [] | ||||||
|  |         dir  = self.get_current_directory() | ||||||
|  |         for file in self.files: | ||||||
|  |             icon = self.create_icon(dir, file).get_pixbuf() | ||||||
|  |             data.append([icon, file]) | ||||||
|  |  | ||||||
|  |         return data | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def get_gtk_icon_str_combo(self): | ||||||
|  |         data = [] | ||||||
|  |         dir  = self.get_current_directory() | ||||||
|  |         for file in self.files: | ||||||
|  |             icon = self.create_icon(dir, file) | ||||||
|  |             data.append([icon, file[0]]) | ||||||
|  |  | ||||||
|  |         return data | ||||||
|  |  | ||||||
|  |     def get_current_directory(self): | ||||||
|  |         return self.get_path() | ||||||
|  |  | ||||||
|  |     def get_current_sub_path(self): | ||||||
|  |         path = self.get_path() | ||||||
|  |         home = self.get_home() + "/" | ||||||
|  |         return path.replace(home, "") | ||||||
|  |  | ||||||
|  |     def get_end_of_path(self): | ||||||
|  |         parts = self.get_current_directory().split("/") | ||||||
|  |         size  = len(parts) | ||||||
|  |         return parts[size - 1] | ||||||
|  |  | ||||||
|  |     def get_dot_dots(self): | ||||||
|  |         return self.hash_set(['.', '..']) | ||||||
|  |  | ||||||
|  |     def get_files(self): | ||||||
|  |         return self.hash_set(self.files) | ||||||
|  |  | ||||||
|  |     def get_dirs(self): | ||||||
|  |         return self.hash_set(self.dirs) | ||||||
|  |  | ||||||
|  |     def get_videos(self): | ||||||
|  |         return self.hash_set(self.vids) | ||||||
|  |  | ||||||
|  |     def get_images(self): | ||||||
|  |         return self.hash_set(self.images) | ||||||
|  |  | ||||||
|  |     def get_desktops(self): | ||||||
|  |         return self.hash_set(self.desktop) | ||||||
|  |  | ||||||
|  |     def get_ungrouped(self): | ||||||
|  |         return self.hash_set(self.ungrouped) | ||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | from .utils import * | ||||||
|  | from .icons import * | ||||||
|  |  | ||||||
|  | from .Path  import Path | ||||||
|  | from .View  import View | ||||||
| @@ -3,13 +3,10 @@ import os, subprocess, threading, hashlib | |||||||
| from os.path import isfile | from os.path import isfile | ||||||
| 
 | 
 | ||||||
| # Gtk imports | # Gtk imports | ||||||
| import gi |  | ||||||
| gi.require_version('GdkPixbuf', '2.0') |  | ||||||
| from gi.repository import GdkPixbuf | from gi.repository import GdkPixbuf | ||||||
| 
 | 
 | ||||||
| # Application imports | # Application imports | ||||||
| from .mixins.desktopiconmixin import DesktopIconMixin | from .mixins import * | ||||||
| from .mixins.videoiconmixin import VideoIconMixin |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def threaded(fn): | def threaded(fn): | ||||||
| @@ -20,7 +17,7 @@ def threaded(fn): | |||||||
| 
 | 
 | ||||||
| class Icon(DesktopIconMixin, VideoIconMixin): | class Icon(DesktopIconMixin, VideoIconMixin): | ||||||
|     def create_icon(self, dir, file): |     def create_icon(self, dir, file): | ||||||
|         full_path = f"{dir}/{file}" |         full_path = dir + "/" + file | ||||||
|         return self.get_icon_image(dir, file, full_path) |         return self.get_icon_image(dir, file, full_path) | ||||||
| 
 | 
 | ||||||
|     def get_icon_image(self, dir, file, full_path): |     def get_icon_image(self, dir, file, full_path): | ||||||
| @@ -39,32 +36,29 @@ class Icon(DesktopIconMixin, VideoIconMixin): | |||||||
|             return None |             return None | ||||||
| 
 | 
 | ||||||
|     def create_thumbnail(self, dir, file): |     def create_thumbnail(self, dir, file): | ||||||
|         full_path = f"{dir}/{file}" |         full_path = dir + "/" + file | ||||||
|         try: |         try: | ||||||
|             file_hash    = hashlib.sha256(str.encode(full_path)).hexdigest() |             file_hash    = hashlib.sha256(str.encode(full_path)).hexdigest() | ||||||
|             hash_img_pth = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg" |             hash_img_pth = self.ABS_THUMBS_PTH + "/" + file_hash + ".jpg" | ||||||
|             if isfile(hash_img_pth) == False: |             if isfile(hash_img_pth) == False: | ||||||
|                 self.generate_video_thumbnail(full_path, hash_img_pth) |                 self.generate_video_thumbnail(full_path, hash_img_pth) | ||||||
| 
 | 
 | ||||||
|             thumbnl = self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH) |             thumbnl = self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH) | ||||||
|             if thumbnl == None: # If no icon whatsoever, return internal default |             if thumbnl == None: # If no icon whatsoever, return internal default | ||||||
|                 thumbnl = GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png") |                 thumbnl = GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICONS + "/video.png") | ||||||
| 
 | 
 | ||||||
|             return thumbnl |             return thumbnl | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|             print("Thumbnail generation issue:") |             print("Thumbnail generation issue:") | ||||||
|             print( repr(e) ) |             print( repr(e) ) | ||||||
|             return GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png") |             return GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICONS + "/video.png") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def create_scaled_image(self, path, wxh): |     def create_scaled_image(self, path, wxh): | ||||||
|         try: |         try: | ||||||
|                 if path.lower().endswith(".gif"): |             pixbuf        = GdkPixbuf.Pixbuf.new_from_file(path) | ||||||
|                     return  GdkPixbuf.PixbufAnimation.new_from_file(path) \ |             scaled_pixbuf = pixbuf.scale_simple(wxh[0], wxh[1], 2)  # 2 = BILINEAR and is best by default | ||||||
|                                                         .get_static_image() \ |             return scaled_pixbuf | ||||||
|                                                         .scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR) |  | ||||||
|                 else: |  | ||||||
|                     return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, wxh[0], wxh[1], True) |  | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|             print("Image Scaling Issue:") |             print("Image Scaling Issue:") | ||||||
|             print( repr(e) ) |             print( repr(e) ) | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user