Samstag, 24. Juli 2010

Explorer-Erweiterung (shell extension) mit Python

Die Problemstellung: Eine Verzeichnisstruktur soll automatisch erstellt werden, und zwar über einen Eintrag im Context-Menü des Explorers.
Die ganze Vorgeschichte ist hier.
Nun die Lösung in Python. Das ganze ist recht übersichtlich:

# -*- coding: utf-8 -*-

#   Erstellt einen Eintrag im Context-Menü des Explorers: CCD-Verzeichnis erstellen
#   bei Click wird im gewählten Verzeichnis eine CCD-Verzeichnisstrukur
#   (bin, build, lib, source, resources) erstellt.

import os.path
import pythoncom
from win32com.shell import shell, shellcon
import win32gui
import win32con

IContextMenu_Methods = ["QueryContextMenu", "InvokeCommand", "GetCommandString"]
IShellExtInit_Methods = ["Initialize"]

#   HKCR Key   Affected object types
#      *    All files
#AllFileSystemObjects  All regular files and file folders
#     Folder   All folders, virtual and filesystem
#     Directory   File folders
#Directory\Background  Directory-Background (Folder is open, one clicks on the white background...)
#      Drive   Root folders of all system drives
#    Network   Entire network
#   NetShare   All network shares

TYPES = [
 'Directory\\Background',
 'Directory',
 ]
SUBKEY = 'CCD-Verzeichnis'

CCDFOLDERS = [
 'source',
 'bin',
 'build',
 'lib',
 'resource',
 ]

def alertError(hwnd, exc):
 win32gui.MessageBox(hwnd, str(exc), str(exc.__class__), win32con.MB_OK)


class ShellExtension:
 _reg_progid_ = "CCD.Verzeichnisersteller.ShellExtension.ContextMenu"
 _reg_desc_ = "CCD-Verzeichnis Shell Extension (context menu)"
 _reg_clsid_ = "{5C664DC4-5ADA-4385-9DEB-EDB51320A668}"

 _com_interfaces_ = [shell.IID_IShellExtInit, shell.IID_IContextMenu]
 _public_methods_ = IContextMenu_Methods + IShellExtInit_Methods

 def Initialize(self, folder, dataobj, hkey):
  self.dataobj = dataobj

 def QueryContextMenu(self, hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags):
  try:
   msg = 'CCD-Verzeichnis ersellen'

   idCmd = idCmdFirst
   items = []
   if (uFlags & 0x000F) == shellcon.CMF_NORMAL:
    items.append(msg)
   elif uFlags & shellcon.CMF_VERBSONLY:
    items.append(msg)
   elif uFlags & shellcon.CMF_EXPLORE:
    items.append(msg)
   else:
    pass
    
   win32gui.InsertMenu(hMenu, indexMenu,
        win32con.MF_SEPARATOR|win32con.MF_BYPOSITION,
        0, None)
   indexMenu += 1
   for item in items:
    win32gui.InsertMenu(hMenu, indexMenu,
         win32con.MF_STRING|win32con.MF_BYPOSITION,
         idCmd, item)
    indexMenu += 1
    idCmd += 1

   win32gui.InsertMenu(hMenu, indexMenu,
        win32con.MF_SEPARATOR|win32con.MF_BYPOSITION,
        0, None)
   indexMenu += 1
   return idCmd-idCmdFirst

  except Exception, e:
   alertError(None, e)
   raise


 def InvokeCommand(self, ci):
  mask, hwnd, verb, params, dir, nShow, hotkey, hicon = ci
  
  try:
   if self.dataobj is None:
    #background-click
    fname = dir
   else:
    #get Files from dragObject
    files = self.getDragFiles()
    if not files:
     return
    fname = files[0]

   self.CreateCCDFolderStructure(hwnd, fname)
  except Exception, e:
   alertError(hwnd, e)
   raise
  return


 def GetCommandString(self, cmd, typ):
  return "Erstellt eine CCD-Verzeichnisstruktur"


 def getDragFiles(self):
  # Format the DataObject using a formatec, then get DragQueryFile from it...
  format_etc = win32con.CF_HDROP, None, 1, -1, pythoncom.TYMED_HGLOBAL
  sm = self.dataobj.GetData(format_etc)
  num_files = shell.DragQueryFile(sm.data_handle, -1)
  files = [shell.DragQueryFile(sm.data_handle, i) for i in range(num_files)]
  return files
  
  
 def CreateCCDFolderStructure(self, hwnd, folder):
  ok = win32gui.MessageBox(hwnd, u'wirklich eine CCD-Struktur ersellen in\n' + folder,
   u'Erstellen bestägen', 
   win32con.MB_YESNO|win32con.MB_ICONQUESTION|win32con.MB_TASKMODAL|win32con.MB_SETFOREGROUND)
  if ok != win32con.IDYES:
   return
  
  if not os.path.isdir(folder):
   alertError(hwnd, folder + u'\nist kein Verzeichnis. Abgebrochen.')
  
  try:
   for ccd in CCDFOLDERS:
    dir = os.path.join(folder, ccd)
    os.mkdir(dir)
  except Exception, e:
   alertError(hwnd, e)
   raise
  return 

def DllRegisterServer():
 import _winreg
 for typ in TYPES:
  key = _winreg.CreateKey(_winreg.HKEY_CLASSES_ROOT, "%s\\shellex" % typ)
  subkey = _winreg.CreateKey(key, "ContextMenuHandlers")
  subkey2 = _winreg.CreateKey(subkey, SUBKEY)
  _winreg.SetValueEx(subkey2, None, 0, _winreg.REG_SZ, ShellExtension._reg_clsid_)
 print ShellExtension._reg_desc_, "registration complete."


def DllUnregisterServer():
 import _winreg
 for typ in TYPES:
  try:
   key = _winreg.DeleteKey(_winreg.HKEY_CLASSES_ROOT, "%s\\shellex\\ContextMenuHandlers\\%s" % (typ, SUBKEY))
  except WindowsError, details:
   import errno
   if details.errno != errno.ENOENT:
    raise
 print ShellExtension._reg_desc_, "unregistration complete."

 
def main(argv):
 from win32com.server import register
 register.UseCommandLine(ShellExtension,
       finalize_register = DllRegisterServer,
       finalize_unregister = DllUnregisterServer)

if __name__=='__main__':
 import sys
 main(sys.argv)

Für die Verwendung muss Python installiert sein (Empfehlung: 2.6) und passend zu der Python-Version PyWin32 (Empfehlung: 214.win32-py2.6)

Die Python-Datei in ein „kluges“ Verzeichnis legen (z.B. %ProgramFiles%\ccd‑verzeichnis\) und in der registry registrieren per doppelklick, oder über die Eingabeaufforderung mit ccd-verzeichnis.py --register. Wenn das ganze wieder entfernt werden soll, so geht dies nur über die Eingabeaufforderung mit ccd-verzeichnis.py --unregister.

Viel Spass.

Edits:
- Das ganze gibt's natürlich auch als Nautilus-Erweiterung...
- Vorlage für den Code war eine Beispiel-Anwendung des PyWin32-Projektes

Keine Kommentare:

Kommentar veröffentlichen