ADD: RabbitVCS context menu integration

This commit is contained in:
Alexander Koblov 2014-07-05 08:47:54 +00:00
commit b028dcb971
4 changed files with 153 additions and 334 deletions

View file

@ -49,17 +49,6 @@ type
ob_type: PPyTypeObject;
end;
function PythonLoadModule(const ModuleName: UTF8String): PPyObject;
function PythonRunFunction(Module: PPyObject; const FunctionName: UTF8String; FileList: TStrings): UTF8String;
var
HasPython: Boolean = False;
implementation
uses
dynlibs, dl;
var
// pythonrun.h
Py_Initialize: procedure; cdecl;
@ -74,16 +63,37 @@ var
// abstract.h
PyObject_CallObject: function(callable_object, args: PPyObject): PPyObject; cdecl;
PyObject_CallFunctionObjArgs: function(callable: PPyObject): PPyObject; cdecl; varargs;
PyObject_CallMethodObjArgs: function(o, name: PPyObject): PPyObject; cdecl; varargs;
// stringobject.h
PyString_AsString: function(ob: PPyObject): PAnsiChar; cdecl;
PyString_FromString: function(s: PAnsiChar): PPyObject; cdecl;
// listobject.h
PyList_New: function(size: csize_t): PPyObject; cdecl;
PyList_Size: function (ob: PPyObject): csize_t; cdecl;
PyList_GetItem: function(ob: PPyObject; index: csize_t): PPyObject; cdecl;
PyList_SetItem: function(ob: PPyObject; index: csize_t; item: PPyObject): cint; cdecl;
// tupleobject.h
PyTuple_New: function(size: csize_t): PPyObject; cdecl;
PyTuple_SetItem: function(ob: PPyObject; index: csize_t; item: PPyObject): cint; cdecl;
procedure Py_DECREF(op: PPyObject);
procedure Py_XDECREF(op: PPyObject);
function PyStringToString(S: PPyObject): String;
procedure PythonAddModulePath(const Path: UTF8String);
function PythonLoadModule(const ModuleName: UTF8String): PPyObject;
function PythonRunFunction(Module: PPyObject; const FunctionName, FunctionArg: UTF8String): PPyObject; overload;
function PythonRunFunction(Module: PPyObject; const FunctionName: UTF8String; FileList: TStrings): PPyObject; overload;
var
HasPython: Boolean = False;
implementation
uses
dynlibs, dl;
procedure Py_DECREF(op: PPyObject);
begin
with op^ do begin
@ -99,6 +109,16 @@ begin
if Assigned(op) then Py_DECREF(op);
end;
function PyStringToString(S: PPyObject): String;
begin
if not Assigned(S) then
Result:= EmptyStr
else begin
Result:= StrPas(PyString_AsString(S));
Py_DECREF(S);
end;
end;
function StringsToPyList(Strings: TStrings): PPyObject;
var
I: LongInt;
@ -123,43 +143,57 @@ begin
end;
end;
procedure PythonAddModulePath(const Path: UTF8String);
begin
PyRun_SimpleString('import sys');
PyRun_SimpleString(PAnsiChar('sys.path.append("' + Path + '")'));
end;
function PythonLoadModule(const ModuleName: UTF8String): PPyObject;
var
pyName: PPyObject;
begin
PyRun_SimpleString('import sys');
PyRun_SimpleString('sys.path.append("")');
pyName:= PyString_FromString(PAnsiChar(ModuleName));
Result:= PyImport_Import(pyName);
Py_DECREF(pyName);
end;
function PythonRunFunction(Module: PPyObject; const FunctionName: UTF8String; FileList: TStrings): UTF8String;
function PythonCallFunction(Module: PPyObject; const FunctionName: UTF8String; FunctionArg: PPyObject): PPyObject; overload;
var
pyFunc, pyList: PPyObject;
pyArgs, pyValue: PPyObject;
pyFunc, pyArgs: PPyObject;
begin
if Assigned(Module) then
begin
pyFunc:= PyObject_GetAttrString(Module, PAnsiChar(FunctionName));
if (Assigned(pyFunc) and (PyCallable_Check(pyFunc) <> 0)) then
begin
pyList:= StringsToPyList(FileList);
pyArgs:= PyObjectsToPyTuple([pyList]);
pyValue:= PyObject_CallObject(pyFunc, pyArgs);
Py_XDECREF(pyList);
pyArgs:= PyObjectsToPyTuple([FunctionArg]);
Result:= PyObject_CallObject(pyFunc, pyArgs);
Py_XDECREF(pyArgs);
if (pyValue = nil) then
if (Result = nil) then begin
PyErr_Print()
else begin
Result:= StrPas(PyString_AsString(pyValue));
Py_DECREF(pyValue);
end;
Py_DECREF(pyFunc);
end;
end;
end;
function PythonRunFunction(Module: PPyObject; const FunctionName, FunctionArg: UTF8String): PPyObject;
var
pyArgs: PPyObject;
begin
pyArgs:= PyString_FromString(PAnsiChar(FunctionArg));
Result:= PythonCallFunction(Module, FunctionName, pyArgs);
end;
function PythonRunFunction(Module: PPyObject; const FunctionName: UTF8String; FileList: TStrings): PPyObject;
var
pyArgs: PPyObject;
begin
pyArgs:= StringsToPyList(FileList);
Result:= PythonCallFunction(Module, FunctionName, pyArgs);
end;
var
libpython: TLibHandle;
@ -177,10 +211,13 @@ begin
@PyCallable_Check:= SafeGetProcAddress(libpython, 'PyCallable_Check');
@PyObject_GetAttrString:= SafeGetProcAddress(libpython, 'PyObject_GetAttrString');
@PyObject_CallObject:= SafeGetProcAddress(libpython, 'PyObject_CallObject');
@PyObject_CallMethodObjArgs:= SafeGetProcAddress(libpython, 'PyObject_CallMethodObjArgs');
@PyObject_CallFunctionObjArgs:= SafeGetProcAddress(libpython, 'PyObject_CallFunctionObjArgs');
@PyString_AsString:= SafeGetProcAddress(libpython, 'PyString_AsString');
@PyString_FromString:= SafeGetProcAddress(libpython, 'PyString_FromString');
@PyList_New:= SafeGetProcAddress(libpython, 'PyList_New');
@PyList_Size:= SafeGetProcAddress(libpython, 'PyList_Size');
@PyList_GetItem:= SafeGetProcAddress(libpython, 'PyList_GetItem');
@PyList_SetItem:= SafeGetProcAddress(libpython, 'PyList_SetItem');
@PyTuple_New:= SafeGetProcAddress(libpython, 'PyTuple_New');
@PyTuple_SetItem:= SafeGetProcAddress(libpython, 'PyTuple_SetItem');

View file

@ -60,9 +60,7 @@ const
function CheckStatus(Path: UTF8String; Recurse: Boolean32 = False;
Invalidate: Boolean32 = True; Summary: Boolean32 = False): string;
function GenerateMenuConditions(Paths: TStringList): string;
procedure FillRabbitMenu(Menu: TPopupMenu; OnClick: TNotifyEvent; Paths: TStringList);
procedure FillRabbitMenu(Menu: TPopupMenu; Paths: TStringList);
var
RabbitVCS: Boolean = False;
@ -70,11 +68,17 @@ var
implementation
uses
dbus, unixtype, fpjson, jsonparser, unix, uDCUtils;
dbus, unixtype, fpjson, jsonparser, unix,
uGlobs, uGlobsPaths, uPython;
const
MODULE_NAME = 'rabbit-vcs';
var
error: DBusError;
conn: PDBusConnection;
conn: PDBusConnection = nil;
PythonModule: PPyObject = nil;
ShellContextMenu: PPyObject = nil;
procedure Print(const sMessage: String);
begin
@ -93,10 +97,7 @@ begin
Result := False;
end;
function CheckService: Boolean;
const
RunStatusChecker = 'echo "from rabbitvcs.services.checkerservice import StatusCheckerStub' +
#13 + 'status_checker = StatusCheckerStub()' + #13 + '" | python';
function CheckService(const PythonScript: UTF8String): Boolean;
var
service_exists: dbus_bool_t;
begin
@ -111,7 +112,7 @@ begin
Print('Service found running.')
else
begin
Result:= fpSystem(RunStatusChecker) = 0;
Result:= fpSystem(PythonScript) = 0;
if Result then
Print('Service successfully started.');
end;
@ -225,107 +226,25 @@ begin
end;
end;
function GenerateMenuConditions(Paths: TStringList): string;
procedure MenuClickHandler(Self, Sender: TObject);
var
I: Integer;
Return: Boolean;
StringPtr: PAnsiChar;
optsPChar: PAnsiChar;
message: PDBusMessage;
pending: PDBusPendingCall;
argsIter, arrayIter: DBusMessageIter;
pyMethod, pyArgs: PPyObject;
MenuItem: TMenuItem absolute Sender;
begin
if not RabbitVCS then Exit;
// Create a new method call and check for errors
message := dbus_message_new_method_call(RabbitVCSAddress, // target for the method call
RabbitVCSObject, // object to call on
RabbitVCSInterface, // interface to call on
'GenerateMenuConditions'); // method name
if (message = nil) then
if Assigned(ShellContextMenu) then
begin
Print('Cannot create message "GenerateMenuConditions"');
Exit;
end;
try
dbus_message_iter_init_append(message, @argsIter);
Return := dbus_message_iter_open_container(@argsIter, DBUS_TYPE_ARRAY, PChar(DBUS_TYPE_STRING_AS_STRING), @arrayIter) <> 0;
if Return then
begin
for I := 0 to Paths.Count - 1 do
begin
optsPChar := PAnsiChar(Paths[I]);
if dbus_message_iter_append_basic(@arrayIter, DBUS_TYPE_STRING, @optsPChar) = 0 then
begin
Print('Cannot append arguments');
Exit;
end;
end;
if dbus_message_iter_close_container(@argsIter, @arrayIter) = 0 then
begin
Print('Cannot append arguments');
Exit;
end;
end;
// Send message and get a handle for a reply
if (dbus_connection_send_with_reply(conn, message, @pending, -1) = 0) then // -1 is default timeout
begin
Print('Error sending message');
Exit;
end;
if (pending = nil) then
begin
Print('Pending call is null');
Exit;
end;
dbus_connection_flush(conn);
finally
dbus_message_unref(message);
end;
// Block until we recieve a reply
dbus_pending_call_block(pending);
// Get the reply message
message := dbus_pending_call_steal_reply(pending);
// Free the pending message handle
dbus_pending_call_unref(pending);
if (message = nil) then
begin
Print('Reply is null');
Exit;
end;
try
// Read the parameters
if (dbus_message_iter_init(message, @argsIter) <> 0) then
begin
if (dbus_message_iter_get_arg_type(@argsIter) = DBUS_TYPE_STRING) then
begin
dbus_message_iter_get_basic(@argsIter, @StringPtr);
Result:= StrPas(StringPtr);
end;
end;
finally
dbus_message_unref(message);
pyMethod:= PyString_FromString('Execute');
pyArgs:= PyString_FromString(PAnsiChar(MenuItem.Hint));
PyObject_CallMethodObjArgs(ShellContextMenu, pyMethod, pyArgs, nil);
Py_XDECREF(pyArgs);
Py_XDECREF(pyMethod);
end;
end;
procedure FillRabbitMenu(Menu: TPopupMenu; OnClick: TNotifyEvent; Paths: TStringList);
procedure FillRabbitMenu(Menu: TPopupMenu; Paths: TStringList);
var
I: Integer;
Parameters,
Conditions: String;
MenuItem,
SubMenu: TMenuItem;
JAnswer : TJSONObject;
Handler: TMethod;
pyMethod, pyValue, pyArgs: PPyObject;
procedure SetBitmap(Item: TMenuItem; const IconName: String);
var
@ -344,189 +263,81 @@ var
end;
end;
procedure AddSeparator(MenuTarget: TMenuItem);
procedure BuildMenu(pyMenu: PPyObject; BaseItem: TMenuItem);
var
Index: Integer;
IconName: String;
MenuItem: TMenuItem;
pyItem, pyObject: PPyObject;
begin
MenuItem:= TMenuItem.Create(Menu);
MenuItem.Caption:= '-';
MenuTarget.Add(MenuItem);
for Index:= 0 to PyList_Size(pyMenu) - 1 do
begin
pyItem:= PyList_GetItem(pyMenu, Index);
MenuItem:= TMenuItem.Create(BaseItem);
pyObject:= PyObject_GetAttrString(pyItem, 'label');
MenuItem.Caption:= PyStringToString(pyObject);
if MenuItem.Caption <> '-' then
begin
pyObject:= PyObject_GetAttrString(pyItem, 'identifier');
MenuItem.Hint:= PyStringToString(pyObject);
if Length(MenuItem.Hint) > 0 then begin
MenuItem.OnClick:= TNotifyEvent(Handler);
end;
pyObject:= PyObject_GetAttrString(pyItem, 'icon');
IconName:= PyStringToString(pyObject);
if Length(IconName) > 0 then SetBitmap(MenuItem, IconName);
end;
pyObject:= PyObject_GetAttrString(pyItem, 'menu');
if Assigned(pyObject) and (PyList_Size(pyObject) > 0) then
begin
BuildMenu(pyObject, MenuItem);
Py_DECREF(pyObject);
end;
BaseItem.Add(MenuItem);
end;
end;
begin
Conditions:= GenerateMenuConditions(Paths);
for I := 0 to Paths.Count - 1 do
Parameters := Parameters + ' ' + QuoteStr(Paths[I]);
with TJSONParser.Create(Conditions) do
try
JAnswer:= Parse as TJSONObject;
with JAnswer do
try
// (MenuUpdate, None),
if (Booleans['is_in_a_or_a_working_copy'] and
Booleans['is_versioned'] and
not Booleans['is_added']) then
begin
MenuItem:= TMenuItem.Create(Menu);
MenuItem.OnClick:= OnClick;
MenuItem.Caption:= 'Update';
MenuItem.Hint:= 'rabbitvcs update' + Parameters;
SetBitmap(MenuItem, 'rabbitvcs-update');
Menu.Items.Add(MenuItem);
end;
// (MenuCommit, None),
if Booleans['is_svn'] and Booleans['is_in_a_or_a_working_copy'] and
(Booleans['is_dir'] or Booleans['is_added'] or Booleans['is_modified'] or
Booleans['is_deleted'] or not Booleans['is_versioned']) then
begin
MenuItem:= TMenuItem.Create(Menu);
MenuItem.OnClick:= OnClick;
MenuItem.Caption:= 'Commit';
MenuItem.Hint:= 'rabbitvcs commit' + Parameters;
SetBitmap(MenuItem, 'rabbitvcs-commit');
Menu.Items.Add(MenuItem);
end;
// (MenuRabbitVCSSvn, [
if Booleans['is_svn'] or not Booleans['is_in_a_or_a_working_copy'] then
begin
SubMenu:= TMenuItem.Create(Menu);
SubMenu.Caption:= 'RabbitVCS SVN';
SetBitmap(SubMenu, 'rabbitvcs');
Menu.Items.Add(SubMenu);
// (MenuCheckout, None),
if (Integers['length'] = 1) and Booleans['is_dir'] and
not Booleans['is_working_copy'] then
begin
MenuItem:= TMenuItem.Create(Menu);
MenuItem.OnClick:= OnClick;
MenuItem.Caption:= 'Checkout...';
MenuItem.Hint:= 'rabbitvcs checkout' + Parameters;
SetBitmap(MenuItem, 'rabbitvcs-checkout');
SubMenu.Add(MenuItem);
end;
// (MenuCompareTool, None),
if ((Integers['length'] = 1) and
Booleans['is_in_a_or_a_working_copy'] and
(Booleans['is_modified'] or Booleans['has_modified'] or
Booleans['is_conflicted'] or Booleans['has_conflicted'])) then
begin
MenuItem:= TMenuItem.Create(Menu);
MenuItem.OnClick:= OnClick;
MenuItem.Caption:= 'Compare with base';
MenuItem.Hint:= 'rabbitvcs diff -s' + Parameters;
SetBitmap(MenuItem, 'rabbitvcs-compare');
SubMenu.Add(MenuItem);
end;
// (MenuShowLog, None),
if ((Integers['length'] = 1) and
Booleans['is_in_a_or_a_working_copy'] and
Booleans['is_versioned'] and
not Booleans['is_added']) then
begin
MenuItem:= TMenuItem.Create(Menu);
MenuItem.OnClick:= OnClick;
MenuItem.Caption:= 'Show Log';
MenuItem.Hint:= 'rabbitvcs log' + Parameters;
SetBitmap(MenuItem, 'rabbitvcs-show_log');
SubMenu.Add(MenuItem);
end;
// (MenuRepoBrowser, None),
begin
MenuItem:= TMenuItem.Create(Menu);
MenuItem.OnClick:= OnClick;
MenuItem.Caption:= 'Repository Browser';
MenuItem.Hint:= 'rabbitvcs browser' + Parameters;
SetBitmap(MenuItem, 'system-search');
SubMenu.Add(MenuItem);
end;
// (MenuCheckForModifications, None),
if (Booleans['is_working_copy'] or
Booleans['is_versioned']) then
begin
MenuItem:= TMenuItem.Create(Menu);
MenuItem.OnClick:= OnClick;
MenuItem.Caption:= 'Check for Modifications...';
MenuItem.Hint:= 'rabbitvcs checkmods' + Parameters;
SetBitmap(MenuItem, 'rabbitvcs-checkmods');
SubMenu.Add(MenuItem);
end;
// (MenuSeparator, None),
AddSeparator(SubMenu);
// (MenuAdd, None),
if (Booleans['is_svn'] and
((Booleans['is_dir'] and Booleans['is_in_a_or_a_working_copy']) or
((not Booleans['is_dir']) and Booleans['is_in_a_or_a_working_copy'] and
not Booleans['is_versioned']) )) then
begin
MenuItem:= TMenuItem.Create(Menu);
MenuItem.OnClick:= OnClick;
MenuItem.Caption:= 'Add';
MenuItem.Hint:= 'rabbitvcs add' + Parameters;
SetBitmap(MenuItem, 'rabbitvcs-add');
SubMenu.Add(MenuItem);
end;
// (MenuSeparator, None),
AddSeparator(SubMenu);
// (MenuUpdateToRevision, None),
if ((Integers['length'] = 1) and
Booleans['is_versioned'] and
Booleans['is_in_a_or_a_working_copy']) then
begin
MenuItem:= TMenuItem.Create(Menu);
MenuItem.OnClick:= OnClick;
MenuItem.Caption:= 'Update to revision...';
MenuItem.Hint:= 'rabbitvcs updateto' + Parameters;
SetBitmap(MenuItem, 'rabbitvcs-update');
SubMenu.Add(MenuItem);
end;
// (MenuRename, None),
if ((Integers['length'] = 1) and
Booleans['is_in_a_or_a_working_copy'] and
Booleans['is_versioned']) then
begin
MenuItem:= TMenuItem.Create(Menu);
MenuItem.OnClick:= OnClick;
MenuItem.Caption:= 'Rename...';
MenuItem.Hint:= 'rabbitvcs rename' + Parameters;
SetBitmap(MenuItem, 'rabbitvcs-rename');
SubMenu.Add(MenuItem);
end;
// (MenuDelete, None),
if (Booleans['exists'] or Booleans['is_versioned']) and
not Booleans['is_deleted'] then
begin
MenuItem:= TMenuItem.Create(Menu);
MenuItem.OnClick:= OnClick;
MenuItem.Caption:= 'Delete';
MenuItem.Hint:= 'rabbitvcs delete' + Parameters;
SetBitmap(MenuItem, 'rabbitvcs-delete');
SubMenu.Add(MenuItem);
end;
end;
except
Exit;
Py_XDECREF(ShellContextMenu);
ShellContextMenu:= PythonRunFunction(PythonModule, 'GetContextMenu', Paths);
if Assigned(ShellContextMenu) then
begin
Handler.Data:= Menu;
Handler.Code:= @MenuClickHandler;
pyMethod:= PyString_FromString('GetMenu');
pyValue:= PyObject_CallMethodObjArgs(ShellContextMenu, pyMethod, nil);
if Assigned(pyValue) then
begin
BuildMenu(pyValue, Menu.Items);
Py_DECREF(pyValue);
end;
JAnswer.Free;
finally
Free;
Py_XDECREF(pyMethod);
end;
end;
procedure Initialize;
var
PythonPath: UTF8String;
begin
dbus_error_init(@error);
conn := dbus_bus_get(DBUS_BUS_SESSION, @error);
if CheckError('Cannot acquire connection to DBUS session bus', @error) then
Exit;
RabbitVCS:= CheckService;
PythonPath:= gpExePath + 'tools';
RabbitVCS:= CheckService(PythonPath + PathDelim + MODULE_NAME + '.py');
if RabbitVCS then begin
PythonAddModulePath(PythonPath);
PythonModule:= PythonLoadModule(MODULE_NAME);
end;
end;
procedure Finalize;
begin
dbus_connection_close(conn);
if Assigned(conn) then dbus_connection_unref(conn);
end;
initialization
Initialize;
RegisterInitialization(@Initialize);
finalization
Finalize;

View file

@ -382,7 +382,7 @@ begin
miOpenWith.Add(mi);
{$IF DEFINED(RabbitVCS)}
FillRabbitMenu(Self, OpenWithMenuItemSelect, FileNames);
FillRabbitMenu(Self, FileNames);
{$ENDIF}
finally

View file

@ -42,7 +42,7 @@ class DCMenuItem:
identifier = None
label = None
icon = None
menu = None
menu = []
def connect(self, signal, *callback):
return
@ -59,18 +59,15 @@ class DCContextMenu(MenuBuilder):
if type(item) is MenuSeparator:
menuitem.label = "-"
else:
if item.callback_name != None:
menuitem.identifier = "RabbitVCS::" + item.callback_name
else:
menuitem.identifier = item.identifier
menuitem.label = item.make_label()
menuitem.icon = item.icon
menuitem.label = item.make_label()
menuitem.identifier = item.callback_name
return menuitem
def attach_submenu(self, menu_node, submenu_list):
menu_node.menu = []
menu_node.identifier = None
menu_node.identifier = ""
for item in submenu_list:
menu_node.menu.append(item)
@ -80,22 +77,15 @@ class DCContextMenu(MenuBuilder):
class DCMainContextMenu(MainContextMenu):
"""Double Commander main context menu class"""
def BuildMenu(self, menu):
result = ""
for item in menu:
result += "<item caption=\"%s\">\n" % item.label
if item.identifier != None:
result += "<command>%s</command>\n" % item.identifier
if item.icon != None:
result += "<icon>" + item.icon + "</icon>\n"
if item.menu != None:
result += self.BuildMenu(item.menu)
result += "</item>\n"
return result
def Execute(self, identifier):
# Try to find and execute callback function
if hasattr(self.callbacks, identifier):
function = getattr(self.callbacks, identifier)
if callable(function):
function(self, None)
def GetXmlMenu(self):
menu = DCContextMenu(self.structure, self.conditions, self.callbacks).menu
return "<menu>\n" + self.BuildMenu(menu) + "</menu>"
def GetMenu(self):
return DCContextMenu(self.structure, self.conditions, self.callbacks).menu
def GetContextMenu(paths):
upaths = []
@ -104,28 +94,9 @@ def GetContextMenu(paths):
sender = DCSender()
base_dir = os.path.dirname(upaths[0])
return DCMainContextMenu(sender, base_dir, upaths, None).GetXmlMenu()
def Execute(identifier, paths):
sender = DCSender()
base_dir = os.path.dirname(paths[0])
vcs_client = rabbitvcs.vcs.create_vcs_instance()
action = MenuItem.make_default_name(identifier)
callbacks = MainContextMenuCallbacks(sender, base_dir, vcs_client, paths)
# Try to find and execute callback function
if hasattr(callbacks, action):
function = getattr(callbacks, action)
if callable(function):
function(sender, None)
return DCMainContextMenu(sender, base_dir, upaths, None)
if __name__ == "__main__":
args = sys.argv
argc = len(args)
status_checker = StatusCheckerStub()
if argc < 2:
status_checker = StatusCheckerStub()
elif (argc > 2):
args.pop(0)
identifier = args.pop(0)
Execute(identifier, args)