#! /usr/bin/env python2 # -*- coding: UTF-8 -*- """ Mass Storage Cloner writes an image file to many mass storage devices with a gui (C) 2014 Martin Süfke License: GPLv3 or newer: http://www.gnu.org/licenses/gpl.html """ """ History: 2014-10-14 started 2015-03-26 added write.sh quick hack for LIP SS2015, released on redmine.fsmpi.rwth-aachen.de """ # Proper logging from pprint import pprint, pformat import logging # SPE compatible logging format logging.basicConfig(level=logging.DEBUG,format='%(levelno)2d:%(funcName)s:%(message)s:File "%(filename)s", line %(lineno)d') logger = logging.getLogger(__name__) #import stacktrace import inspect #import Tkinter #as Tk try: import ttk as Tk # Tk.Tk = Tkinter.Tk # need a Tk.Tk to make the root Window. Faking one. except: logger.info("Tiling Toolkit (ttk) not found. Falling back to classic Tk") import Tkinter as Tk #from Tkinter import BooleanVar, IntVar, DoubleVar, StringVar #import tkMessageBox as dialog import tkFileDialog as filedialog from Tkconstants import N,E,S,W,HORIZONTAL,TOP,BOTTOM,BOTH, NORMAL, DISABLED from vtk import VCheckbutton, VSLabel, VIScale, Vset, VGridFrame#, Vget import dbus import dbusdetect import gobject import StoreObjFrames import os.path import sys import threading import Queue LastSavedFile=None def DoLoadFile(): filename = filedialog.askopenfilename( filetypes=[ ("Image files", "*.img"), # TODO: zipped/bzipped/xzipped images ? # ("All files", "*.*"), ("All files", "*"), ], title="Select image file to write" ) logger.debug( "Open dialog returns >>>%s<<< ",str(filename)) if (len(filename)==0) or (str(filename).strip("\0\t\r\n ")==""): return def Implement(msg=""): logger.warn('Implement File "%s", line %s, %s'+msg,*inspect.stack()[1][1:4]) class ThreadEventedQueue(Queue.Queue): def __init__(self, EventName, TkRoot=None, maxsize=0): # Cannot get super() to work ?!? #super(ThreadEventedQueue, self ).__init__(maxsize) Queue.Queue.__init__(self, maxsize) self.EventName = '<<{0:s}>>'.format(EventName) self.TkRoot = TkRoot def AddEvent(self, EventObj): self.put(EventObj) if self.TkRoot is not None: self.TkRoot.event_generate(self.EventName, when='tail') def SetRoot(self, TkRoot, Callback): self.TkRoot = TkRoot self.TkRoot.bind(self.EventName, Callback) class MSClonerGui(threading.Thread): """ Separate Thread according to http://stackoverflow.com/questions/459083/how-do-you-run-your-own-code-alongside-tkinters-event-loop """ def __init__(self, autostart=True, NotifierThread = None): threading.Thread.__init__(self) self.NotifierThread = NotifierThread self.ThreadNotifyQueue = ThreadEventedQueue('MyUDisk2Notify', None) #self.DoQuit = False if autostart: self.start() def BuildGui(self): try: self.root = Tk.Tk() except AttributeError: self.root = Tk.Tkinter.Tk() #build the GUI on 'root' self.root.wm_title("MassStorageCloner - GPLv3 or newer (C) 2014 Martin Süfke") FrameHead = VGridFrame(self.root, relief='raised') self.CbRemovableOnly = VCheckbutton(master=FrameHead, text="Removable only", command=self.OnCbRemovableOnly) FrameContent = Tk.Frame(self.root) FrameContDevice = Tk.Frame(FrameContent) self.root.columnconfigure(0,weight=1) self.root.rowconfigure(0,weight=1) FrameHead.grid(column=0, row=0, sticky=(N,E,W)) FrameHead.grid_next([self.CbRemovableOnly, Tk.Button(master=FrameHead, text="Unhide ignored", command=self.OnUnhideIgnored), ]) FrameContent.grid(column=0, row=1, sticky=(N,S,E,W)) FrameContent.columnconfigure(0,weight=1) FrameContent.rowconfigure(0,weight=1) FrameContDevice.grid(sticky=(N,E,S,W)) self.TKStorageFrame = StoreObjFrames.TkFStorage(FrameContDevice) dbusdetect.ScanDevices(self.TKStorageFrame) def run(self): logger.debug("GUI thread run()") #self.root.update() self.BuildGui() logger.debug("GUI thread built GUI") # Activate Event system self.ThreadNotifyQueue.SetRoot(self.root, self.ThreadNotifyCallback) #if not self.DoQuit: self.root.mainloop() logger.debug("Tk mainloop() ended") if self.NotifierThread is not None: if self.NotifierThread.is_alive(): self.NotifierThread.quit() else: logger.debug('NotifierThread already dead') def quit(self): #self.DoQuit = False #try: self.root.quit() #except: # pass def OnCbRemovableOnly(self): """ CbRemovableOnly has been checked or unchecked """ #logger.debug("CbRemovableOnly(): %s", self.CbRemovableOnly.value ) if self.CbRemovableOnly.value: self.TKStorageFrame.FilterViewDriveAdd({'Removable':False}) else: self.TKStorageFrame.FilterViewDriveRemove(['Removable', ]) def OnUnhideIgnored(self): """ Unhide Ignore has been clicked""" self.TKStorageFrame.FilterViewDriveRemove(['Id', ]) def SignalDBusEvent(self, EventObj): """ This can be called by the DBus notifier thread """ assert self.ThreadNotifyQueue is not None self.ThreadNotifyQueue.AddEvent(EventObj) def ThreadNotifyCallback(self, event): try: while True: ev = self.ThreadNotifyQueue.get_nowait() logger.debug("GUI Thread %s %s", ev, event) self.ProcessNotifyEvent(ev) self.ThreadNotifyQueue.task_done() except Queue.Empty: pass def ProcessNotifyEvent(self, event): """ event is a tuple containing at least an Identifier in event[0] called by ThreadNotifyCallback() """ assert len(event)>0, "Event tuple length >0 required, got {0!s}".format(event) if event[0] == 'MountPointsChanged': #assert len(event)>2, "Event tuple length >2 required, got {0!s}".format(event) assert len(event)>2, "Event tuple length >2 required, got {0!s}".format(event) self.TKStorageFrame.UpdateMountPoints(event[1], event[2], dbusdetect.UnmountByUDisks) elif event[0] == 'DriveAdded': assert len(event)>2, "Event tuple length >2 required, got {0!s}".format(event) self.TKStorageFrame.AddDrive(event[1], event[2], dbusdetect.EjectByUDisks, dbusdetect.PowerOffByUDisks) elif event[0] == 'BlockDeviceAdded': assert len(event)>2, "Event tuple length >2 required, got {0!s}".format(event) self.TKStorageFrame.AddBlockDevice(event[1], event[2]) elif event[0] == 'FileSystemAdded': assert len(event)>2, "Event tuple length >2 required, got {0!s}".format(event) self.TKStorageFrame.AddFileSystem(event[1],event[2], dbusdetect.MountByUDisks, dbusdetect.UnmountByUDisks) elif event[0] == 'DriveRemoved': #self.TKStorageFrame.AddDrive(event[1], event[2], dbusdetect.EjectByUDisks, dbusdetect.PowerOffByUDisks) assert len(event)>1, "Event tuple length >1 required, got {0!s}".format(event) self.TKStorageFrame.RemoveDrive(event[1]) elif event[0] == 'BlockDeviceRemoved': #self.TKStorageFrame.AddBlockDevice(event[1], event[2]) assert len(event)>1, "Event tuple length >1 required, got {0!s}".format(event) self.TKStorageFrame.RemoveBlockDevice(event[1]) elif event[0] == 'FileSystemRemoved': assert len(event)>1, "Event tuple length >1 required, got {0!s}".format(event) self.TKStorageFrame.RemoveFileSystem(event[1]) #self.TKStorageFrame.AddFileSystem(event[1],event[2], dbusdetect.MountByUDisks, dbusdetect.UnmountByUDisks) else: logger.warn("Unknown Event %s",event[0]) class MSGObjectNotifier(threading.Thread): """ Another thread to contain the GObject mainloop """ def __init__(self, GuiThread = None): threading.Thread.__init__(self) self.GuiThread = GuiThread def run(self): logger.debug("GObject thread started") try: gobject.threads_init() # the key to make gobject threads run in python self.loop = gobject.MainLoop() self.context = self.loop.get_context() self.InstallDBusListener() logger.debug("running GObject mainloop()") self.loop.run() finally: logger.debug("GObject mainloop() ended") if self.GuiThread is not None: if self.GuiThread.is_alive(): self.GuiThread.quit() else: logger.debug('GuiThread already dead') def quit(self): self.loop.quit() def InstallDBusListener(self): dbusdetect.InstallChangeReceiver( PropertiesChangedHandler=self.UDisks2Signal_PropertiesChanged, InterfaceAddedHander=self.UDisks2Signal_InterfaceAdded, InterfaceRemovedHandler=self.UDisks2Signal_InterfaceRemoved, ) def UDisks2Signal_PropertiesChanged(self, *args, **kwargs): """ Callback for any DBus.Properties -> PropertiesChanged """ # print "UDisks2 Properties Changed: {0!s} {1!s}\n".format(args,pformat(kwargs)) if (len(args) >= 3) and (len(kwargs) >= 1): # May have args: subject, message, signature (TODO: How are these defined in DBus ?) if 'org.freedesktop.UDisks2.Filesystem' == args[0]: if isinstance(args[1],dict) and ('MountPoints' in args[1]): # don't care about args[2] / Signature U2MountPoints=args[1].get('MountPoints',[]) #logger.debug('U2Path: %s %s',type(kwargs), pformat(kwargs)) U2Path=kwargs.get('path','') #logger.debug('U2Path: %s',U2Path) if U2Path.startswith('/org/freedesktop/UDisks2/block_devices/'): # Have a change in Block Devices self.SignalGuiMountPointChanged(U2Path, U2MountPoints) return assert logger is not None logger.info("Unhandled Message %s %s\n", args, pformat(kwargs)) def UDisks2Signal_InterfaceAdded(self, *args, **kwargs): """ Callback for any UDisks2.ObjectManager -> InterfaceAdded Through this call go drives and blockdevices that are added """ if (len(args) >= 2): U2Path = args[0] if isinstance(args[1], dict): U2Dict = args[1] #logger.debug("U2Dict: %s\n", pformat(U2Dict)) else: U2Dict = {} if U2Path.startswith('/org/freedesktop/UDisks2/drives/'): #logger.debug("Drive added %s\n", args[0].rsplit('/')[-1]) DriveDict = U2Dict.get('org.freedesktop.UDisks2.Drive') if DriveDict is not None: self.SignalGuiDriveAdded(U2Path, DriveDict) return elif U2Path.startswith('/org/freedesktop/UDisks2/block_devices/'): #logger.debug("Blockdevice added %s\n", args[0].rsplit('/')[-1]) BlockDict = U2Dict.get('org.freedesktop.UDisks2.Block') if BlockDict is not None: self.SignalGuiBlockDeviceAdded(U2Path, BlockDict) FsDict = U2Dict.get('org.freedesktop.UDisks2.Filesystem') if FsDict is not None: self.SignalGuiFileSystemAdded(U2Path, FsDict) if (BlockDict is not None) or (FsDict is not None): return assert logger is not None logger.info("Unknown %s %s\n", args, pformat(kwargs)) def UDisks2Signal_InterfaceRemoved(self, *args, **kwargs): """ Callback for any UDisks2.ObjectManager -> InterfaceRemoved """ if (len(args) >= 2): U2Path = args[0] if isinstance(args[1], list): U2List = args[1] else: U2List = [] Unknown = True if U2Path.startswith('/org/freedesktop/UDisks2/block_devices/'): if 'org.freedesktop.UDisks2.Filesystem' in U2List: self.SignalGuiFileSystemRemoved(U2Path) Unknown = False if 'org.freedesktop.UDisks2.Block' in U2List: self.SignalGuiBlockDeviceRemoved(U2Path) Unknown = False if U2Path.startswith('/org/freedesktop/UDisks2/drives/'): #logger.debug("Drive removed %s\n", args[0].rsplit('/')[-1]) if 'org.freedesktop.UDisks2.Drive' in U2List: self.SignalGuiDriveRemoved(U2Path) Unknown = False if not Unknown: return assert logger is not None logger.info("Unknown %s %s\n", args, pformat(kwargs)) def SignalGuiMountPointChanged(self, U2Path, U2MountPoints): logger.debug("GObj() path %s mp %s\n", U2Path, U2MountPoints) EvObj=('MountPointsChanged', U2Path, U2MountPoints) self.GuiThread.SignalDBusEvent(EvObj) def SignalGuiDriveAdded(self, U2Path, U2Dict): logger.debug("GObj() path %s u2d.k %s\n", U2Path, U2Dict.keys()) EvObj=('DriveAdded', U2Path, U2Dict) self.GuiThread.SignalDBusEvent(EvObj) def SignalGuiBlockDeviceAdded(self, U2Path, U2Dict): logger.debug("GObj() path %s u2d.k %s\n", U2Path, U2Dict.keys()) EvObj=('BlockDeviceAdded', U2Path, U2Dict) self.GuiThread.SignalDBusEvent(EvObj) def SignalGuiFileSystemAdded(self, U2Path, U2Dict): logger.debug("GObj() path %s u2d.k %s\n", U2Path, U2Dict.keys()) EvObj=('FileSystemAdded', U2Path, U2Dict) self.GuiThread.SignalDBusEvent(EvObj) def SignalGuiEventRemoved(self, name, param1): EvObj=(name, param1) self.GuiThread.SignalDBusEvent(EvObj) def SignalGuiDriveRemoved(self, U2Path): self.SignalGuiEventRemoved('DriveRemoved', U2Path) def SignalGuiBlockDeviceRemoved(self, U2Path): self.SignalGuiEventRemoved('BlockDeviceRemoved', U2Path) def SignalGuiFileSystemRemoved(self, U2Path): self.SignalGuiEventRemoved('FileSystemRemoved', U2Path) gnotifier = MSGObjectNotifier() gui = MSClonerGui(NotifierThread=gnotifier) gnotifier.GuiThread = gui gnotifier.start() #dbusdetect.InstallChangeReceiver() #logger.debug('Time to do more in the main thread') # Do not know what to do here (sensibly) to wait ... trying join() #import time #while gui.is_alive() and gnotifier.is_alive(): # time.sleep(1) # logger.debug('tick ') # Waiting for a key press is not smart either #logger.info("Waiting for key press on stdin") #sys.stdin.read(1) #gnotifier.quit() #gui.quit() logger.debug('Waiting for thread joins') gnotifier.join() gui.join() # don't know: join() again or not ? logger.debug('End, threads joined') """make use of figure.ginput() for mouse input or waitforbuttonpress() for keyboard-stuff """ def _quit(self): logger.debug("called python _quit()") if gui is not None: gui.quit() # stops mainloop gui.root.destroy() # this is necessary on Windows to prevent # Fatal Python Error: PyEval_RestoreThread: NULL tstate #end;