diff --git a/include/lgi/common/SubProcess.h b/include/lgi/common/SubProcess.h --- a/include/lgi/common/SubProcess.h +++ b/include/lgi/common/SubProcess.h @@ -1,134 +1,134 @@ /** \file \brief Sub-process wrapper. This class runs one or more sub-processes chained together by pipes. Example: LSubProcess p1("ls", "-l"); LSubProcess p2("grep", "string"); p1.Connect(&p2); p1.Start(true, false); int r; char Buf[256]; while ((r = p1.Read(Buf, sizeof(Buf))) > 0) { // So something with 'Buf' } */ #ifndef _SUB_PROCESS_H_ #define _SUB_PROCESS_H_ #ifdef WIN32 #define USE_SIMPLE_FORK 0 #else #define USE_SIMPLE_FORK 1 #endif #if USE_SIMPLE_FORK #include #endif #if defined(MAC) #include - #define GSUBPROCESS_ERROR EBADEXEC + #define LSUBPROCESS_ERROR EBADEXEC #elif defined(LINUX) || defined(HAIKU) #include - #define GSUBPROCESS_ERROR ECHILD + #define LSUBPROCESS_ERROR ECHILD #elif defined(WINDOWS) - #define GSUBPROCESS_ERROR ERROR_PROCESS_ABORTED + #define LSUBPROCESS_ERROR ERROR_PROCESS_ABORTED #endif LgiExtern bool LIsProcess(OsProcessId Pid); class LgiClass LSubProcess : public LStream { public: #if defined(WIN32) typedef HANDLE PipeHandle; typedef DWORD ProcessId; #else typedef int PipeHandle; typedef pid_t ProcessId; #endif union Pipe { PipeHandle Handles[2]; struct { PipeHandle Read; PipeHandle Write; }; Pipe(); bool Create ( #ifdef WIN32 LPSECURITY_ATTRIBUTES pAttr #else void *UnusedParam #endif ); void Close(); }; protected: struct LSubProcessPriv *d; friend struct LSubProcessPriv; struct Variable { LString Var, Val; }; Variable *GetEnvVar(const char *Var, bool Create = false); bool Dupe(PipeHandle Old, PipeHandle New); LSubProcess *Parent, *Child; public: // Object LSubProcess(const char *exe, const char *args = NULL, bool pseudoConsole = false); ~LSubProcess(); // Environment void SetInitFolder(const char *f); const char *GetEnvironment(const char *Var); bool SetEnvironment(const char *Var, const char *Value); bool SetUser(const char *User, const char *Pass); // Dom (support StreamReadable/StreamWritable) bool GetValue(const char *Var, LVariant &Value) override; // Handles void SetStdin(PipeHandle Hnd); void SetStdout(PipeHandle Hnd); // Process lifecycle bool GetNewGroup(); void SetNewGroup(bool ng); ProcessId Handle(); bool IsRunning(); uint32_t GetErrorCode(); int32 GetExitValue(); void Connect(LSubProcess *child); bool Start(bool ReadAccess = true, bool WriteAccess = false, bool MapStderrToStdout = true); int Wait(); bool Interrupt(); bool Kill(); bool Signal(int which); int32 Communicate(LStreamI *Out, LStreamI *In = NULL, LCancel *Cancel = NULL); // IO int Peek(); LString Read(); ssize_t Read(void *Buf, ssize_t Size, int Flags = 0) override; bool Write(LString s); ssize_t Write(const void *Buf, ssize_t Size, int Flags = 0) override; }; #endif diff --git a/lvc/mac/LvcCocoa.xcodeproj/project.pbxproj b/lvc/mac/LvcCocoa.xcodeproj/project.pbxproj --- a/lvc/mac/LvcCocoa.xcodeproj/project.pbxproj +++ b/lvc/mac/LvcCocoa.xcodeproj/project.pbxproj @@ -1,583 +1,583 @@ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 48; objects = { /* Begin PBXBuildFile section */ 342D52A50F0CBA2F002A1C7C /* LgiMain.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 342D52A40F0CBA2F002A1C7C /* LgiMain.cpp */; }; 3448F8C62081D2500038AE76 /* mac-icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 3448F8C52081D2500038AE76 /* mac-icon.icns */; }; 3449AB5828B96C8A00F8AAD4 /* SshConnection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3449AB5728B96C8A00F8AAD4 /* SshConnection.cpp */; }; 3457FE492081CB3D00014BE4 /* BrowseUi.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3457FE3E2081CB3D00014BE4 /* BrowseUi.cpp */; }; 3457FE4A2081CB3D00014BE4 /* DropDownBtn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3457FE3F2081CB3D00014BE4 /* DropDownBtn.cpp */; }; 3457FE4B2081CB3D00014BE4 /* Main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3457FE412081CB3D00014BE4 /* Main.cpp */; }; 3457FE4C2081CB3D00014BE4 /* VcCommit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3457FE432081CB3D00014BE4 /* VcCommit.cpp */; }; 3457FE4D2081CB3D00014BE4 /* VcFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3457FE452081CB3D00014BE4 /* VcFile.cpp */; }; 3457FE4E2081CB3D00014BE4 /* VcFolder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3457FE472081CB3D00014BE4 /* VcFolder.cpp */; }; 3457FE502081CBE500014BE4 /* SubProcess.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3457FE4F2081CBE500014BE4 /* SubProcess.cpp */; }; 3457FE522081CC4800014BE4 /* Lvc.lr8 in Resources */ = {isa = PBXBuildFile; fileRef = 3457FE512081CC4800014BE4 /* Lvc.lr8 */; }; 3465A7E32B2AB1BF00084F66 /* PostBuildStep.py in Resources */ = {isa = PBXBuildFile; fileRef = 3465A7E22B2AB1BF00084F66 /* PostBuildStep.py */; }; - 346F5AC72BA507C000D64829 /* libssh.4.7.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 34DB96442BA5060A0058A51D /* libssh.4.7.2.dylib */; }; + 346F5AC72BA507C000D64829 /* libssh.4.9.6.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 34DB96442BA5060A0058A51D /* libssh.4.9.6.dylib */; }; 347D1489252D453300422DA9 /* DeEscape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 347D1488252D453300422DA9 /* DeEscape.cpp */; }; 3485E7F5294F13F70020C14D /* PatchViewer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3485E7F4294F13F70020C14D /* PatchViewer.cpp */; }; 34A3CCB92B64647E000AD22F /* libpng16.16.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34A3CCB52B646469000AD22F /* libpng16.16.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 34A3CCBA2B646487000AD22F /* libz.1.3.1.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34A3CCB72B646472000AD22F /* libz.1.3.1.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 34BD35B120C5484A0001838A /* ControlTree.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34BD35B020C5484A0001838A /* ControlTree.cpp */; }; 34BD35B320C548710001838A /* XmlTreeUi.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34BD35B220C548710001838A /* XmlTreeUi.cpp */; }; 34C032B32319CDE900470DF2 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34C032B22319CDE900470DF2 /* Cocoa.framework */; }; 34C032BB2319CE8C00470DF2 /* LgiCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34C032B92319CDFE00470DF2 /* LgiCocoa.framework */; }; 34C032BD2319CEC100470DF2 /* OptionsFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34C032BC2319CEC100470DF2 /* OptionsFile.cpp */; }; 34C032BE2319CEF200470DF2 /* LgiCocoa.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34C032B92319CDFE00470DF2 /* LgiCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 34CE07102327B3B100807DCB /* Png.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CE070F2327B3B100807DCB /* Png.cpp */; }; - 34DB96462BA5061E0058A51D /* libssh.4.7.2.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34DB96442BA5060A0058A51D /* libssh.4.7.2.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + 34DB96462BA5061E0058A51D /* libssh.4.9.6.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34DB96442BA5060A0058A51D /* libssh.4.9.6.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 34E1910C20B1898E00005243 /* image-list.png in Resources */ = {isa = PBXBuildFile; fileRef = 34E1910B20B1898E00005243 /* image-list.png */; }; 8D0C4E8D0486CD37000505A6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0867D6AAFE840B52C02AAC07 /* InfoPlist.strings */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 34C032B82319CDFE00470DF2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34C032B42319CDFE00470DF2 /* LgiCocoa.xcodeproj */; proxyType = 2; remoteGlobalIDString = 3477C2681CBF020F0028B84B; remoteInfo = LgiCocoa; }; 34C032BF2319CF1A00470DF2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34C032B42319CDFE00470DF2 /* LgiCocoa.xcodeproj */; proxyType = 1; remoteGlobalIDString = 3477C2671CBF020F0028B84B; remoteInfo = LgiCocoa; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 34640D3212D5D8AB00B207F4 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( - 34DB96462BA5061E0058A51D /* libssh.4.7.2.dylib in CopyFiles */, + 34DB96462BA5061E0058A51D /* libssh.4.9.6.dylib in CopyFiles */, 34A3CCBA2B646487000AD22F /* libz.1.3.1.dylib in CopyFiles */, 34A3CCB92B64647E000AD22F /* libpng16.16.dylib in CopyFiles */, 34C032BE2319CEF200470DF2 /* LgiCocoa.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 342D52A40F0CBA2F002A1C7C /* LgiMain.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LgiMain.cpp; path = ../../src/common/Lgi/LgiMain.cpp; sourceTree = SOURCE_ROOT; }; 3448F8C52081D2500038AE76 /* mac-icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = "mac-icon.icns"; path = "../Resources/mac-icon.icns"; sourceTree = ""; }; 3449AB5628B96C8A00F8AAD4 /* SshConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SshConnection.h; path = ../Src/SshConnection.h; sourceTree = ""; }; 3449AB5728B96C8A00F8AAD4 /* SshConnection.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SshConnection.cpp; path = ../Src/SshConnection.cpp; sourceTree = ""; }; 3457FE3E2081CB3D00014BE4 /* BrowseUi.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BrowseUi.cpp; path = ../src/BrowseUi.cpp; sourceTree = ""; }; 3457FE3F2081CB3D00014BE4 /* DropDownBtn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = DropDownBtn.cpp; path = ../Src/DropDownBtn.cpp; sourceTree = ""; }; 3457FE402081CB3D00014BE4 /* Lvc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Lvc.h; path = ../Src/Lvc.h; sourceTree = ""; }; 3457FE412081CB3D00014BE4 /* Main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Main.cpp; path = ../Src/Main.cpp; sourceTree = ""; }; 3457FE422081CB3D00014BE4 /* resdefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = resdefs.h; path = ../Resources/resdefs.h; sourceTree = ""; }; 3457FE432081CB3D00014BE4 /* VcCommit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = VcCommit.cpp; path = ../Src/VcCommit.cpp; sourceTree = ""; }; 3457FE442081CB3D00014BE4 /* VcCommit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VcCommit.h; path = ../Src/VcCommit.h; sourceTree = ""; }; 3457FE452081CB3D00014BE4 /* VcFile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = VcFile.cpp; path = ../Src/VcFile.cpp; sourceTree = ""; }; 3457FE462081CB3D00014BE4 /* VcFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VcFile.h; path = ../Src/VcFile.h; sourceTree = ""; }; 3457FE472081CB3D00014BE4 /* VcFolder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = VcFolder.cpp; path = ../Src/VcFolder.cpp; sourceTree = ""; }; 3457FE482081CB3D00014BE4 /* VcFolder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VcFolder.h; path = ../Src/VcFolder.h; sourceTree = ""; }; 3457FE4F2081CBE500014BE4 /* SubProcess.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; name = SubProcess.cpp; path = ../../src/posix/SubProcess.cpp; sourceTree = ""; }; 3457FE512081CC4800014BE4 /* Lvc.lr8 */ = {isa = PBXFileReference; explicitFileType = text; fileEncoding = 4; name = Lvc.lr8; path = ../Resources/Lvc.lr8; sourceTree = ""; }; 3465A7E22B2AB1BF00084F66 /* PostBuildStep.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = PostBuildStep.py; sourceTree = ""; }; 347D1488252D453300422DA9 /* DeEscape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = DeEscape.cpp; path = ../../src/common/Text/DeEscape.cpp; sourceTree = ""; }; 3485E7F4294F13F70020C14D /* PatchViewer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PatchViewer.cpp; path = ../Src/PatchViewer.cpp; sourceTree = ""; }; 3488EE99233C16AF00756838 /* en */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 34A3CCB52B646469000AD22F /* libpng16.16.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libpng16.16.dylib; path = /opt/local/lib/libpng16.16.dylib; sourceTree = ""; }; 34A3CCB72B646472000AD22F /* libz.1.3.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.1.3.1.dylib; path = /opt/local/lib/libz.1.3.1.dylib; sourceTree = ""; }; 34BD35B020C5484A0001838A /* ControlTree.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ControlTree.cpp; path = ../../src/common/Widgets/ControlTree.cpp; sourceTree = ""; }; 34BD35B220C548710001838A /* XmlTreeUi.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = XmlTreeUi.cpp; path = ../../src/common/Text/XmlTreeUi.cpp; sourceTree = ""; }; 34C032B22319CDE900470DF2 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = ../../../../../../../System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 34C032B42319CDFE00470DF2 /* LgiCocoa.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = LgiCocoa.xcodeproj; path = ../../src/mac/cocoa/LgiCocoa.xcodeproj; sourceTree = ""; }; 34C032BC2319CEC100470DF2 /* OptionsFile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = OptionsFile.cpp; path = ../../src/common/Lgi/OptionsFile.cpp; sourceTree = ""; }; 34CE070F2327B3B100807DCB /* Png.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Png.cpp; path = ../../src/common/Gdc2/Filters/Png.cpp; sourceTree = ""; }; - 34DB96442BA5060A0058A51D /* libssh.4.7.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libssh.4.7.2.dylib; path = ../../../../../codelib/libssh/build/src/libssh.4.7.2.dylib; sourceTree = ""; }; + 34DB96442BA5060A0058A51D /* libssh.4.9.6.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libssh.4.9.6.dylib; path = /opt/local/lib/libssh.4.9.6.dylib; sourceTree = ""; }; 34E1910B20B1898E00005243 /* image-list.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-list.png"; path = "../Resources/image-list.png"; sourceTree = ""; }; 8D0C4E960486CD37000505A6 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 8D0C4E970486CD37000505A6 /* Lvc.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Lvc.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 8D0C4E910486CD37000505A6 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 34C032BB2319CE8C00470DF2 /* LgiCocoa.framework in Frameworks */, 34C032B32319CDE900470DF2 /* Cocoa.framework in Frameworks */, - 346F5AC72BA507C000D64829 /* libssh.4.7.2.dylib in Frameworks */, + 346F5AC72BA507C000D64829 /* libssh.4.9.6.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 195DF8CFFE9D517E11CA2CBB /* Products */ = { isa = PBXGroup; children = ( 8D0C4E970486CD37000505A6 /* Lvc.app */, ); name = Products; sourceTree = ""; }; 20286C29FDCF999611CA2CEA /* i.Hex */ = { isa = PBXGroup; children = ( 20286C2AFDCF999611CA2CEA /* Sources */, 20286C2CFDCF999611CA2CEA /* Resources */, 20286C32FDCF999611CA2CEA /* External Frameworks and Libraries */, 195DF8CFFE9D517E11CA2CBB /* Products */, 347D1483252D450500422DA9 /* Frameworks */, ); name = i.Hex; sourceTree = ""; }; 20286C2AFDCF999611CA2CEA /* Sources */ = { isa = PBXGroup; children = ( 3457FE412081CB3D00014BE4 /* Main.cpp */, 3457FE432081CB3D00014BE4 /* VcCommit.cpp */, 3457FE452081CB3D00014BE4 /* VcFile.cpp */, 3457FE472081CB3D00014BE4 /* VcFolder.cpp */, 3457FE402081CB3D00014BE4 /* Lvc.h */, 3457FE442081CB3D00014BE4 /* VcCommit.h */, 3457FE462081CB3D00014BE4 /* VcFile.h */, 3457FE482081CB3D00014BE4 /* VcFolder.h */, 342D52A30F0CBA07002A1C7C /* Lgi */, 3449AB3D28B96C7800F8AAD4 /* Ssh */, 3485E7F6294F15EE0020C14D /* UI */, ); name = Sources; sourceTree = ""; }; 20286C2CFDCF999611CA2CEA /* Resources */ = { isa = PBXGroup; children = ( 3465A7E22B2AB1BF00084F66 /* PostBuildStep.py */, 3457FE422081CB3D00014BE4 /* resdefs.h */, 34E1910B20B1898E00005243 /* image-list.png */, 3448F8C52081D2500038AE76 /* mac-icon.icns */, 3457FE512081CC4800014BE4 /* Lvc.lr8 */, 8D0C4E960486CD37000505A6 /* Info.plist */, 0867D6AAFE840B52C02AAC07 /* InfoPlist.strings */, ); name = Resources; sourceTree = ""; }; 20286C32FDCF999611CA2CEA /* External Frameworks and Libraries */ = { isa = PBXGroup; children = ( - 34DB96442BA5060A0058A51D /* libssh.4.7.2.dylib */, + 34DB96442BA5060A0058A51D /* libssh.4.9.6.dylib */, 34A3CCB52B646469000AD22F /* libpng16.16.dylib */, 34A3CCB72B646472000AD22F /* libz.1.3.1.dylib */, 34C032B42319CDFE00470DF2 /* LgiCocoa.xcodeproj */, 34C032B22319CDE900470DF2 /* Cocoa.framework */, ); name = "External Frameworks and Libraries"; sourceTree = ""; }; 342D52A30F0CBA07002A1C7C /* Lgi */ = { isa = PBXGroup; children = ( 347D1488252D453300422DA9 /* DeEscape.cpp */, 34CE070F2327B3B100807DCB /* Png.cpp */, 34C032BC2319CEC100470DF2 /* OptionsFile.cpp */, 34BD35B220C548710001838A /* XmlTreeUi.cpp */, 34BD35B020C5484A0001838A /* ControlTree.cpp */, 3457FE4F2081CBE500014BE4 /* SubProcess.cpp */, 342D52A40F0CBA2F002A1C7C /* LgiMain.cpp */, ); name = Lgi; sourceTree = ""; }; 3449AB3D28B96C7800F8AAD4 /* Ssh */ = { isa = PBXGroup; children = ( 3449AB5728B96C8A00F8AAD4 /* SshConnection.cpp */, 3449AB5628B96C8A00F8AAD4 /* SshConnection.h */, ); name = Ssh; sourceTree = ""; }; 347D1483252D450500422DA9 /* Frameworks */ = { isa = PBXGroup; children = ( ); name = Frameworks; sourceTree = ""; }; 3485E7F6294F15EE0020C14D /* UI */ = { isa = PBXGroup; children = ( 3457FE3E2081CB3D00014BE4 /* BrowseUi.cpp */, 3485E7F4294F13F70020C14D /* PatchViewer.cpp */, 3457FE3F2081CB3D00014BE4 /* DropDownBtn.cpp */, ); name = UI; sourceTree = ""; }; 34C032B52319CDFE00470DF2 /* Products */ = { isa = PBXGroup; children = ( 34C032B92319CDFE00470DF2 /* LgiCocoa.framework */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 8D0C4E890486CD37000505A6 /* Lvc */ = { isa = PBXNativeTarget; buildConfigurationList = C0E91AC508A95435008D54AB /* Build configuration list for PBXNativeTarget "Lvc" */; buildPhases = ( 8D0C4E8C0486CD37000505A6 /* Resources */, 8D0C4E8F0486CD37000505A6 /* Sources */, 8D0C4E910486CD37000505A6 /* Frameworks */, 34640D3212D5D8AB00B207F4 /* CopyFiles */, 347D148F252D51F300422DA9 /* ShellScript */, 3465A7C82B2AB1A600084F66 /* ShellScript */, ); buildRules = ( ); dependencies = ( 34C032C02319CF1A00470DF2 /* PBXTargetDependency */, ); name = Lvc; productInstallPath = "$(HOME)/Applications"; productName = i.Hex; productReference = 8D0C4E970486CD37000505A6 /* Lvc.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 20286C28FDCF999611CA2CEA /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0940; }; buildConfigurationList = C0E91AC908A95435008D54AB /* Build configuration list for PBXProject "LvcCocoa" */; compatibilityVersion = "Xcode 8.0"; developmentRegion = en; hasScannedForEncodings = 1; knownRegions = ( en, Base, ); mainGroup = 20286C29FDCF999611CA2CEA /* i.Hex */; projectDirPath = ""; projectReferences = ( { ProductGroup = 34C032B52319CDFE00470DF2 /* Products */; ProjectRef = 34C032B42319CDFE00470DF2 /* LgiCocoa.xcodeproj */; }, ); projectRoot = ""; targets = ( 8D0C4E890486CD37000505A6 /* Lvc */, ); }; /* End PBXProject section */ /* Begin PBXReferenceProxy section */ 34C032B92319CDFE00470DF2 /* LgiCocoa.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = LgiCocoa.framework; remoteRef = 34C032B82319CDFE00470DF2 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ 8D0C4E8C0486CD37000505A6 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 3457FE522081CC4800014BE4 /* Lvc.lr8 in Resources */, 34E1910C20B1898E00005243 /* image-list.png in Resources */, 8D0C4E8D0486CD37000505A6 /* InfoPlist.strings in Resources */, 3448F8C62081D2500038AE76 /* mac-icon.icns in Resources */, 3465A7E32B2AB1BF00084F66 /* PostBuildStep.py in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3465A7C82B2AB1A600084F66 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "# Type a script or drag a script file from your workspace to insert its path.\npython3 ${PROJECT_DIR}/PostBuildStep.py ${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}\n"; }; 347D148F252D51F300422DA9 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nexport FW=${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}\necho fw=${FW}\nrm -f ${FW}/libssh.4.dylib\nln -s ./libssh.4.7.2.dylib ${FW}/libssh.4.dylib\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 8D0C4E8F0486CD37000505A6 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 347D1489252D453300422DA9 /* DeEscape.cpp in Sources */, 3457FE502081CBE500014BE4 /* SubProcess.cpp in Sources */, 34BD35B320C548710001838A /* XmlTreeUi.cpp in Sources */, 3457FE4C2081CB3D00014BE4 /* VcCommit.cpp in Sources */, 3457FE4E2081CB3D00014BE4 /* VcFolder.cpp in Sources */, 3485E7F5294F13F70020C14D /* PatchViewer.cpp in Sources */, 3457FE4A2081CB3D00014BE4 /* DropDownBtn.cpp in Sources */, 3457FE4D2081CB3D00014BE4 /* VcFile.cpp in Sources */, 34C032BD2319CEC100470DF2 /* OptionsFile.cpp in Sources */, 3457FE492081CB3D00014BE4 /* BrowseUi.cpp in Sources */, 342D52A50F0CBA2F002A1C7C /* LgiMain.cpp in Sources */, 34CE07102327B3B100807DCB /* Png.cpp in Sources */, 34BD35B120C5484A0001838A /* ControlTree.cpp in Sources */, 3449AB5828B96C8A00F8AAD4 /* SshConnection.cpp in Sources */, 3457FE4B2081CB3D00014BE4 /* Main.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 34C032C02319CF1A00470DF2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = LgiCocoa; targetProxy = 34C032BF2319CF1A00470DF2 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 0867D6AAFE840B52C02AAC07 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 3488EE99233C16AF00756838 /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ C0E91AC608A95435008D54AB /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_WEAK = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; HEADER_SEARCH_PATHS = ( ../../include, ../../include/lgi/mac/cocoa, ../../../../../codelib/libssh/include, /opt/local/include, /opt/local/include/libpng16, ); INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Applications"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "../../../../../codelib/libssh/build/**", /opt/local/lib, ); OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "-Wno-nullability-completeness", "-DHAS_LIBSSH=1", ); PRODUCT_BUNDLE_IDENTIFIER = com.memecode.Lvc; PRODUCT_NAME = Lvc; WRAPPER_EXTENSION = app; }; name = Debug; }; C0E91AC708A95435008D54AB /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_WEAK = YES; COMBINE_HIDPI_IMAGES = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_PRECOMPILE_PREFIX_HEADER = YES; HEADER_SEARCH_PATHS = ( ../../include, ../../include/lgi/mac/cocoa, ../../../../../codelib/libssh/include, /opt/local/include, /opt/local/include/libpng16, ); INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Applications"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "../../../../../codelib/libssh/build/**", /opt/local/lib, ); OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "-Wno-nullability-completeness", "-DHAS_LIBSSH=1", ); PRODUCT_BUNDLE_IDENTIFIER = com.memecode.Lvc; PRODUCT_NAME = Lvc; WRAPPER_EXTENSION = app; }; name = Release; }; C0E91ACA08A95435008D54AB /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( MAC, _DEBUG, "LGI_COCOA=1", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( ../../include/common, ../../include/mac/cocoa, ../../../../../CodeLib/libpng, ../../../../../CodeLib/libpng/build, ); MACOSX_DEPLOYMENT_TARGET = 10.15; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; }; name = Debug; }; C0E91ACB08A95435008D54AB /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( MAC, "LGI_COCOA=1", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( ../../include/common, ../../include/mac/cocoa, ../../../../../CodeLib/libpng, ../../../../../CodeLib/libpng/build, ); MACOSX_DEPLOYMENT_TARGET = 10.15; SDKROOT = macosx; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ C0E91AC508A95435008D54AB /* Build configuration list for PBXNativeTarget "Lvc" */ = { isa = XCConfigurationList; buildConfigurations = ( C0E91AC608A95435008D54AB /* Debug */, C0E91AC708A95435008D54AB /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; C0E91AC908A95435008D54AB /* Build configuration list for PBXProject "LvcCocoa" */ = { isa = XCConfigurationList; buildConfigurations = ( C0E91ACA08A95435008D54AB /* Debug */, C0E91ACB08A95435008D54AB /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 20286C28FDCF999611CA2CEA /* Project object */; } diff --git a/lvc/src/VcFolder.cpp b/lvc/src/VcFolder.cpp --- a/lvc/src/VcFolder.cpp +++ b/lvc/src/VcFolder.cpp @@ -1,5155 +1,5155 @@ #include "Lvc.h" #include "lgi/common/Combo.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/Json.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/IniFile.h" #include "resdefs.h" #ifndef CALL_MEMBER_FN #define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember)) #endif #define MAX_AUTO_RESIZE_ITEMS 2000 #define PROFILE_FN 0 #if PROFILE_FN #define PROF(s) Prof.Add(s) #else #define PROF(s) #endif class TmpFile : public LFile { int Status; LString Hint; public: TmpFile(const char *hint = NULL) { Status = 0; if (hint) Hint = hint; else Hint = "_lvc"; } LFile &Create() { LFile::Path p(LSP_TEMP); p += Hint; do { char s[256]; sprintf_s(s, sizeof(s), "../%s%i.tmp", Hint.Get(), LRand()); p += s; } while (p.Exists()); Status = LFile::Open(p.GetFull(), O_READWRITE); return *this; } }; bool TerminalAt(LString Path) { #if defined(MAC) const char *Locations[] = { "/System/Applications/Utilities/Terminal.app", "/Applications/Utilities/Terminal.app", NULL }; for (size_t i=0; Locations[i]; i++) { if (LFileExists(Locations[i])) { LString term; term.Printf("%s/Contents/MacOS/Terminal", Locations[i]); return LExecute(term, Path); } } #elif defined(WINDOWS) TCHAR w[MAX_PATH_LEN]; auto r = GetWindowsDirectory(w, CountOf(w)); if (r > 0) { LFile::Path p = LString(w); p += "system32\\cmd.exe"; FileDev->SetCurrentFolder(Path); return LExecute(p); } #elif defined(LINUX) LExecute("gnome-terminal", NULL, Path); #endif return false; } int Ver2Int(LString v) { auto p = v.Split("."); int i = 0; for (auto s : p) { auto Int = s.Int(); if (Int < 256) { i <<= 8; i |= (uint8_t)Int; } else { LAssert(0); return 0; } } return i; } int ToolVersion[VcMax] = {0}; #define DEBUG_READER_THREAD 0 #if DEBUG_READER_THREAD #define LOG_READER(...) printf(__VA_ARGS__) #else #define LOG_READER(...) #endif ReaderThread::ReaderThread(VersionCtrl vcs, LAutoPtr p, LStream *out) : LThread("ReaderThread") { Vcs = vcs; Process = p; Out = out; Result = -1; FilterCount = 0; // We don't start this thread immediately... because the number of threads is scaled to the system // resources, particularly CPU cores. } ReaderThread::~ReaderThread() { Out = NULL; while (!IsExited()) LSleep(1); } const char *HgFilter = "We\'re removing Mercurial support"; const char *CvsKill = "No such file or directory"; int ReaderThread::OnLine(char *s, ssize_t len) { switch (Vcs) { case VcHg: { if (strnistr(s, HgFilter, len)) FilterCount = 4; if (FilterCount > 0) { FilterCount--; return 0; } else if (LString(s, len).Strip().Equals("remote:")) { return 0; } break; } case VcCvs: { if (strnistr(s, CvsKill, len)) return -1; break; } default: break; } return 1; } bool ReaderThread::OnData(char *Buf, ssize_t &r) { LOG_READER("OnData %i\n", (int)r); #if 1 char *Start = Buf; for (char *c = Buf; c < Buf + r;) { bool nl = *c == '\n'; c++; if (nl) { int Result = OnLine(Start, c - Start); if (Result < 0) { // Kill process and exit thread. Process->Kill(); return false; } if (Result == 0) { ssize_t LineLen = c - Start; ssize_t NextLine = c - Buf; ssize_t Remain = r - NextLine; if (Remain > 0) memmove(Start, Buf + NextLine, Remain); r -= LineLen; c = Start; } else Start = c; } } #endif Out->Write(Buf, r); return true; } int ReaderThread::Main() { bool b = Process->Start(true, false); if (!b) { LString s("Process->Start failed.\n"); Out->Write(s.Get(), s.Length(), ErrSubProcessFailed); return ErrSubProcessFailed; } char Buf[1024]; ssize_t r; LOG_READER("%s:%i - starting reader loop, pid=%i\n", _FL, Process->Handle()); while (Process->IsRunning()) { if (Out) { LOG_READER("%s:%i - starting read.\n", _FL); r = Process->Read(Buf, sizeof(Buf)); LOG_READER("%s:%i - read=%i.\n", _FL, (int)r); if (r > 0) { if (!OnData(Buf, r)) return -1; } } else { Process->Kill(); return -1; break; } } LOG_READER("%s:%i - process loop done.\n", _FL); if (Out) { while ((r = Process->Read(Buf, sizeof(Buf))) > 0) OnData(Buf, r); } LOG_READER("%s:%i - loop done.\n", _FL); Result = (int) Process->GetExitValue(); #if _DEBUG if (Result) printf("%s:%i - Process err: %i 0x%x\n", _FL, Result, Result); #endif return Result; } ///////////////////////////////////////////////////////////////////////////////////////////// int VcFolder::CmdMaxThreads = 0; int VcFolder::CmdActiveThreads = 0; void VcFolder::Init(AppPriv *priv) { if (!CmdMaxThreads) CmdMaxThreads = LAppInst->GetCpuCount(); d = priv; Expanded(false); Insert(Tmp = new LTreeItem); Tmp->SetText("Loading..."); LAssert(d != NULL); } VcFolder::VcFolder(AppPriv *priv, const char *uri) { Init(priv); Uri.Set(uri); GetType(); } VcFolder::VcFolder(AppPriv *priv, LXmlTag *t) { Init(priv); Serialize(t, false); } VcFolder::~VcFolder() { if (d->CurFolder == this) d->CurFolder = NULL; Log.DeleteObjects(); } VersionCtrl VcFolder::GetType() { if (Type == VcNone) Type = d->DetectVcs(this); return Type; } bool VcFolder::IsLocal() { return Uri.IsProtocol("file"); } const char *VcFolder::LocalPath() { if (!Uri.IsProtocol("file") || Uri.sPath.IsEmpty()) { LAssert(!"Shouldn't call this if not a file path."); return NULL; } auto c = Uri.sPath.Get(); #ifdef WINDOWS if (*c == '/') c++; #endif return c; } const char *VcFolder::GetText(int Col) { switch (Col) { case 0: { if (Uri.IsFile()) Cache = LocalPath(); else Cache.Printf("%s%s", Uri.sHost.Get(), Uri.sPath.Get()); if (Cmds.Length()) Cache += " (...)"; return Cache; } case 1: { CountCache.Printf("%i/%i", Unpulled, Unpushed); CountCache = CountCache.Replace("-1", "--"); return CountCache; } } return NULL; } bool VcFolder::Serialize(LXmlTag *t, bool Write) { if (Write) t->SetContent(Uri.ToString()); else { LString s = t->GetContent(); bool isUri = s.Find("://") >= 0; if (isUri) Uri.Set(s); else Uri.SetFile(s); } return true; } LXmlTag *VcFolder::Save() { LXmlTag *t = new LXmlTag(OPT_Folder); if (t) Serialize(t, true); return t; } const char *VcFolder::GetVcName() { if (!VcCmd) VcCmd = d->GetVcName(GetType()); return VcCmd; } char VcFolder::GetPathSep() { if (Uri.IsFile()) return DIR_CHAR; return '/'; // FIXME: Assumption is that the remote system is unix based. } bool VcFolder::RunCmd(const char *Args, LoggingType Logging, std::function Callback) { Result Ret; Ret.Code = -1; const char *Exe = GetVcName(); if (!Exe || CmdErrors > 2) return false; if (Uri.IsFile()) { new ProcessCallback(Exe, Args, LocalPath(), Logging == LogNone ? d->Log : NULL, GetTree()->GetWindow(), Callback); } else { LAssert(!"Impl me."); return false; } return true; } #if HAS_LIBSSH SshConnection::LoggingType Convert(LoggingType t) { switch (t) { case LogNormal: case LogSilo: return SshConnection::LogInfo; case LogDebug: return SshConnection::LogDebug; } return SshConnection::LogNone; } #endif bool VcFolder::StartCmd(const char *Args, ParseFn Parser, ParseParams *Params, LoggingType Logging) { const char *Exe = GetVcName(); if (!Exe) return false; if (CmdErrors > 2) return false; if (Uri.IsFile()) { if (d->Log && Logging != LogSilo) d->Log->Print("%s %s\n", Exe, Args); LAutoPtr Process(new LSubProcess(Exe, Args)); if (!Process) return false; Process->SetInitFolder(Params && Params->AltInitPath ? Params->AltInitPath.Get() : LocalPath()); #if 0//def MAC // Mac GUI apps don't share the terminal path, so this overrides that and make it work auto Path = LGetPath(); if (Path.Length()) { LString Tmp = LString(LGI_PATH_SEPARATOR).Join(Path); printf("Tmp='%s'\n", Tmp.Get()); Process->SetEnvironment("PATH", Tmp); } #endif LString::Array Ctx; Ctx.SetFixedLength(false); Ctx.Add(LocalPath()); Ctx.Add(Exe); Ctx.Add(Args); LAutoPtr c(new Cmd(Ctx, Logging, d->Log)); if (!c) return false; c->PostOp = Parser; c->Params.Reset(Params); c->Rd.Reset(new ReaderThread(GetType(), Process, c)); Cmds.Add(c.Release()); } else { #if HAS_LIBSSH auto c = d->GetConnection(Uri.ToString()); if (!c) return false; if (!c->Command(this, Exe, Args, Parser, Params, Convert(Logging))) return false; #endif } Update(); return true; } int LogDateCmp(LListItem *a, LListItem *b, NativeInt Data) { VcCommit *A = dynamic_cast(a); VcCommit *B = dynamic_cast(b); if ((A != NULL) ^ (B != NULL)) { // This handles keeping the "working folder" list item at the top return (A != NULL) - (B != NULL); } // Sort the by date from most recent to least return -A->GetTs().Compare(&B->GetTs()); } void VcFolder::AddGitName(LString Hash, LString Name) { if (!Hash || !Name) { LAssert(!"Param error"); return; } LString Existing = GitNames.Find(Hash); if (Existing) GitNames.Add(Hash, Existing + "," + Name); else GitNames.Add(Hash, Name); } LString VcFolder::GetGitNames(LString Hash) { LString Short = Hash(0, 11); return GitNames.Find(Short); } bool VcFolder::ParseBranches(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcGit: { LString::Array a = s.SplitDelimit("\r\n"); for (auto &l: a) { LString::Array c; char *s = l.Get(); while (*s && IsWhite(*s)) s++; bool IsCur = *s == '*'; if (IsCur) s++; while (*s && IsWhite(*s)) s++; if (*s == '(') { s++; auto e = strchr(s, ')'); if (e) { c.New().Set(s, e - s); e++; c += LString(e).SplitDelimit(" \t"); } } else { c = LString(s).SplitDelimit(" \t"); } if (c.Length() < 1) { d->Log->Print("%s:%i - Too few parts in line '%s'\n", _FL, l.Get()); continue; } if (IsCur) SetCurrentBranch(c[0]); AddGitName(c[1], c[0]); Branches.Add(c[0], new VcBranch(c[0], c[1])); } break; } case VcHg: { auto a = s.SplitDelimit("\r\n"); for (auto b: a) { if (b.Find("inactive") > 0) continue; auto name = b(0, 28).Strip(); auto refs = b(28, -1).SplitDelimit()[0].SplitDelimit(":"); auto branch = Branches.Find(name); if (branch) branch->Hash = refs.Last(); else Branches.Add(name, new VcBranch(name, refs.Last())); } if (Params && Params->Str.Equals("CountToTip")) CountToTip(); break; } default: { break; } } IsBranches = Result ? StatusError : StatusNone; OnBranchesChange(); return false; } void VcFolder::GetRemoteUrl(std::function Callback) { LAutoPtr p(new ParseParams); p->Callback = Callback; switch (GetType()) { case VcGit: { StartCmd("config --get remote.origin.url", NULL, p.Release()); break; } case VcSvn: { StartCmd("info --show-item=url", NULL, p.Release()); break; } case VcHg: { StartCmd("paths default", NULL, p.Release()); break; } default: break; } } void VcFolder::SelectCommit(LWindow *Parent, LString Commit, LString Path) { bool requireFullMatch = true; if (GetType() == VcGit) requireFullMatch = false; // This function find the given commit and selects it such that the diffs are displayed in the file list VcCommit *ExistingMatch = NULL; for (auto c: Log) { char *rev = c->GetRev(); bool match = requireFullMatch ? Commit.Equals(rev) : Strstr(rev, Commit.Get()) != NULL; if (match) { ExistingMatch = c; break; } } FileToSelect = Path; if (ExistingMatch) { ExistingMatch->Select(true); } else { // If the commit isn't there, it's likely that the log item limit was reached before the commit was // found. In which case we should go get just that commit and add it: d->Files->Empty(); // Diff just that ref: LString a; switch (GetType()) { case VcGit: { a.Printf("diff %s~ %s", Commit.Get(), Commit.Get()); StartCmd(a, &VcFolder::ParseSelectCommit); break; } case VcHg: { a.Printf("log -p -r %s", Commit.Get()); StartCmd(a, &VcFolder::ParseSelectCommit); break; } default: { NoImplementation(_FL); break; } } // if (Parent) LgiMsg(Parent, "The commit '%s' wasn't found", AppName, MB_OK, Commit.Get()); } } bool VcFolder::ParseSelectCommit(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcGit: case VcHg: case VcSvn: case VcCvs: { ParseDiff(Result, s, Params); break; } default: { NoImplementation(_FL); break; } } return false; } void VcFolder::OnBranchesChange() { auto *w = d->Tree->GetWindow(); if (!w || !LTreeItem::Select()) return; if (Branches.Length()) { // Set the colours up LString Default; for (auto b: Branches) { if (!stricmp(b.key, "default") || !stricmp(b.key, "trunk")) Default = b.key; /* else printf("Other=%s\n", b.key); */ } int Idx = 1; for (auto b: Branches) { if (!b.value->Colour.IsValid()) { if (Default && !stricmp(b.key, Default)) b.value->Colour = GetPaletteColour(0); else b.value->Colour = GetPaletteColour(Idx++); } } } UpdateBranchUi(); } void VcFolder::DefaultFields() { if (Fields.Length() == 0) { switch (GetType()) { case VcHg: { Fields.Add(LGraph); Fields.Add(LIndex); Fields.Add(LRevision); Fields.Add(LBranch); Fields.Add(LAuthor); Fields.Add(LTime); Fields.Add(LMessageTxt); break; } case VcGit: { Fields.Add(LGraph); Fields.Add(LRevision); Fields.Add(LBranch); Fields.Add(LAuthor); Fields.Add(LTime); Fields.Add(LMessageTxt); break; } default: { Fields.Add(LGraph); Fields.Add(LRevision); Fields.Add(LAuthor); Fields.Add(LTime); Fields.Add(LMessageTxt); break; } } } } int VcFolder::IndexOfCommitField(CommitField fld) { return (int)Fields.IndexOf(fld); } void VcFolder::UpdateColumns(LList *lst) { if (!lst) lst = d->Commits; lst->EmptyColumns(); for (auto c: Fields) { switch (c) { case LGraph: lst->AddColumn("---", 60); break; case LIndex: lst->AddColumn("Index", 60); break; case LBranch: lst->AddColumn("Branch", 60); break; case LRevision: lst->AddColumn("Revision", 60); break; case LAuthor: lst->AddColumn("Author", 240); break; case LTime: lst->AddColumn("Date", 130); break; case LMessageTxt: lst->AddColumn("Message", 700); break; default: LAssert(0); break; } } } void VcFolder::FilterCurrentFiles() { LArray All; d->Files->GetAll(All); // Update the display property for (auto i: All) { auto fn = i->GetText(COL_FILENAME); bool vis = !d->FileFilter || Stristr(fn, d->FileFilter.Get()); i->GetCss(true)->Display(vis ? LCss::DispBlock : LCss::DispNone); // LgiTrace("Filter '%s' by '%s' = %i\n", fn, d->FileFilter.Get(), vis); } d->Files->Sort(0); d->Files->UpdateAllItems(); d->Files->ResizeColumnsToContent(); } void VcFolder::UpdateAuthorUi() { if (AuthorLocal) d->Wnd()->SetCtrlName(IDC_AUTHOR, AuthorLocal.ToString()); else if (AuthorGlobal) d->Wnd()->SetCtrlName(IDC_AUTHOR, AuthorGlobal.ToString()); } LString VcFolder::GetConfigFile(bool local) { switch (GetType()) { case VcHg: { LFile::Path p; if (local) { p = LFile::Path(LocalPath()) / ".hg" / "hgrc"; } else { p = LFile::Path(LSP_HOME) / ".hgrc"; if (!p.Exists()) p = LFile::Path(LSP_HOME) / "mercurial.ini"; } d->Log->Print("%s: %i\n", p.GetFull().Get(), p.Exists()); if (p.Exists()) return p.GetFull(); break; } default: { NoImplementation(_FL); return false; } } return LString(); } bool VcFolder::GetAuthor(bool local, std::function callback) { auto scope = local ? "--local" : "--global"; auto target = local ? &AuthorLocal : &AuthorGlobal; switch (GetType()) { case VcGit: { if (target->InProgress) return true; auto params = new ParseParams; params->Callback = [this, callback, target](auto code, auto s) { for (auto ln: s.Strip().SplitDelimit("\r\n")) { auto parts = ln.SplitDelimit("=", 1); if (parts.Length() == 2) { if (parts[0].Equals("user.email")) target->email = parts[1]; else if (parts[0].Equals("user.name")) target->name = parts[1]; } } target->InProgress = false; if (callback) callback(target->name, target->email); }; auto args = LString::Fmt("-P config -l %s", scope); target->InProgress = StartCmd(args, NULL, params); break; } case VcHg: { auto config = GetConfigFile(local); if (!config) return false; LIniFile data(config); auto author = data.Get("ui", "username"); auto start = author.Find("<"); auto end = author.Find(">", start); if (start >= 0 && end >= start) { target->name = author(0, start).Strip(); target->email = author(start + 1, end).Strip(); } IsGettingAuthor = false; callback(target->name, target->email); break; } default: { NoImplementation(_FL); return false; } } return true; } bool VcFolder::SetAuthor(bool local, LString name, LString email) { auto scope = local ? "--local" : "--global"; auto target = local ? &AuthorLocal : &AuthorGlobal; target->name = name; target->email = email; switch (GetType()) { case VcGit: { auto args = LString::Fmt("config %s user.name \"%s\"", scope, name.Get()); StartCmd(args); args = LString::Fmt("config %s user.email \"%s\"", scope, email.Get()); StartCmd(args); break; } case VcHg: { auto config = GetConfigFile(local); if (!config) return false; LString author; author.Printf("%s <%s>", name.Get(), email.Get()); LIniFile data(config); data.Set("ui", "username", author); return data.Write(); } default: { NoImplementation(_FL); return false; } } return true; } void VcFolder::Select(bool b) { #if PROFILE_FN LProfile Prof("Select"); #endif if (!b) { auto *w = d->Tree->GetWindow(); w->SetCtrlName(IDC_BRANCH, NULL); } PROF("Parent.Select"); LTreeItem::Select(b); if (b) { if (Uri.IsFile() && !LDirExists(LocalPath())) return; PROF("DefaultFields"); DefaultFields(); if (!AuthorLocal) GetAuthor(true, [this](auto name, auto email) { UpdateAuthorUi(); }); if (!AuthorGlobal) GetAuthor(false, [this](auto name, auto email) { UpdateAuthorUi(); }); UpdateAuthorUi(); PROF("Type Change"); if (GetType() != d->PrevType) { d->PrevType = GetType(); UpdateColumns(); } PROF("UpdateCommitList"); if ((Log.Length() == 0 || CommitListDirty) && !IsLogging) { switch (GetType()) { case VcGit: { LVariant Limit; d->Opts.GetValue("git-limit", Limit); LString cmd = "rev-list --all --header --timestamp --author-date-order", s; if (Limit.CastInt32() > 0) { s.Printf(" -n %i", Limit.CastInt32()); cmd += s; } IsLogging = StartCmd(cmd, &VcFolder::ParseRevList); break; } case VcSvn: { LVariant Limit; d->Opts.GetValue("svn-limit", Limit); if (CommitListDirty) { IsLogging = StartCmd("up", &VcFolder::ParsePull, new ParseParams("log")); break; } LString s; if (Limit.CastInt32() > 0) s.Printf("log --limit %i", Limit.CastInt32()); else s = "log"; IsLogging = StartCmd(s, &VcFolder::ParseLog); break; } case VcHg: { IsLogging = StartCmd("log", &VcFolder::ParseLog); StartCmd("resolve -l", &VcFolder::ParseResolveList); break; } case VcPending: { break; } default: { IsLogging = StartCmd("log", &VcFolder::ParseLog); break; } } CommitListDirty = false; } PROF("GetBranches"); if (GetBranches()) OnBranchesChange(); if (d->CurFolder != this) { PROF("RemoveAll"); d->CurFolder = this; d->Commits->RemoveAll(); } PROF("Uncommit"); if (!Uncommit) Uncommit.Reset(new UncommitedItem(d)); d->Commits->Insert(Uncommit, 0); PROF("Log Loop"); int64 CurRev = Atoi(CurrentCommit.Get()); List Ls; for (auto l: Log) { if (CurrentCommit && l->GetRev()) { switch (GetType()) { case VcSvn: { int64 LogRev = Atoi(l->GetRev()); if (CurRev >= 0 && CurRev >= LogRev) { CurRev = -1; l->SetCurrent(true); } else { l->SetCurrent(false); } break; } default: l->SetCurrent(!_stricmp(CurrentCommit, l->GetRev())); break; } } LList *CurOwner = l->GetList(); if (!CurOwner) Ls.Insert(l); } PROF("Ls Ins"); d->Commits->Insert(Ls); if (d->Resort >= 0) { PROF("Resort"); d->Commits->Sort(LstCmp, d->Resort); d->Resort = -1; } PROF("ColSizing"); if (d->Commits->Length() > MAX_AUTO_RESIZE_ITEMS) { int i = 0; if (GetType() == VcHg && d->Commits->GetColumns() >= 7) { d->Commits->ColumnAt(i++)->Width(60); // LGraph d->Commits->ColumnAt(i++)->Width(40); // LIndex d->Commits->ColumnAt(i++)->Width(100); // LRevision d->Commits->ColumnAt(i++)->Width(60); // LBranch d->Commits->ColumnAt(i++)->Width(240); // LAuthor d->Commits->ColumnAt(i++)->Width(130); // LTimeStamp d->Commits->ColumnAt(i++)->Width(400); // LMessage } else if (d->Commits->GetColumns() >= 5) { d->Commits->ColumnAt(i++)->Width(40); // LGraph d->Commits->ColumnAt(i++)->Width(270); // LRevision d->Commits->ColumnAt(i++)->Width(240); // LAuthor d->Commits->ColumnAt(i++)->Width(130); // LTimeStamp d->Commits->ColumnAt(i++)->Width(400); // LMessage } } else d->Commits->ResizeColumnsToContent(); PROF("UpdateAll"); d->Commits->UpdateAllItems(); PROF("GetCur"); GetCurrentRevision(); } } int CommitRevCmp(VcCommit **a, VcCommit **b) { int64 arev = Atoi((*a)->GetRev()); int64 brev = Atoi((*b)->GetRev()); int64 diff = (int64)brev - arev; if (diff < 0) return -1; return (diff > 0) ? 1 : 0; } int CommitIndexCmp(VcCommit **a, VcCommit **b) { auto ai = (*a)->GetIndex(); auto bi = (*b)->GetIndex(); auto diff = (int64)bi - ai; if (diff < 0) return -1; return (diff > 0) ? 1 : 0; } int CommitDateCmp(VcCommit **a, VcCommit **b) { LTimeStamp ats, bts; (*a)->GetTs().Get(ats); (*b)->GetTs().Get(bts); int64 diff = (int64)bts.Get() - ats.Get(); if (diff < 0) return -1; return (diff > 0) ? 1 : 0; } void VcFolder::GetCurrentRevision(ParseParams *Params) { if (CurrentCommit || IsIdent != StatusNone) return; switch (GetType()) { case VcGit: if (StartCmd("rev-parse HEAD", &VcFolder::ParseInfo, Params)) IsIdent = StatusActive; break; case VcSvn: if (StartCmd("info", &VcFolder::ParseInfo, Params)) IsIdent = StatusActive; break; case VcHg: if (StartCmd("id -i -n", &VcFolder::ParseInfo, Params)) IsIdent = StatusActive; break; case VcCvs: break; default: break; } } bool VcFolder::GetBranches(ParseParams *Params) { if (Branches.Length() > 0 || IsBranches != StatusNone) return true; switch (GetType()) { case VcGit: if (StartCmd("-P branch -v", &VcFolder::ParseBranches, Params)) IsBranches = StatusActive; break; case VcSvn: Branches.Add("trunk", new VcBranch("trunk")); OnBranchesChange(); break; case VcHg: { if (StartCmd("branches", &VcFolder::ParseBranches, Params)) IsBranches = StatusActive; auto p = new ParseParams; p->Callback = [this](auto code, auto str) { SetCurrentBranch(str.Strip()); }; StartCmd("branch", NULL, p); break; } case VcCvs: break; default: break; } return false; } bool VcFolder::ParseRevList(int Result, LString s, ParseParams *Params) { Log.DeleteObjects(); int Errors = 0; switch (GetType()) { case VcGit: { LString::Array Commits; Commits.SetFixedLength(false); // Split on the NULL chars... char *c = s.Get(); char *e = c + s.Length(); while (c < e) { char *nul = c; while (nul < e && *nul) nul++; if (nul <= c) break; Commits.New().Set(c, nul-c); if (nul >= e) break; c = nul + 1; } for (auto Commit: Commits) { LAutoPtr Rev(new VcCommit(d, this)); if (Rev->GitParse(Commit, true)) { Log.Add(Rev.Release()); } else { // LAssert(!"Parse failed."); LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, Commit.Get()); Errors++; } } LinkParents(); break; } default: LAssert(!"Impl me."); break; } IsLogging = false; return Errors == 0; } LString VcFolder::GetFilePart(const char *uri) { LUri u(uri); LString File = u.IsFile() ? u.DecodeStr(u.LocalPath()) : u.sPath(Uri.sPath.Length(), -1).LStrip("/"); return File; } void VcFolder::ClearLog() { Uncommit.Reset(); Log.DeleteObjects(); } void VcFolder::LogFilter(const char *Filter) { if (!Filter) { LAssert(!"No filter."); return; } switch (GetType()) { case VcGit: { // See if 'Filter' is a commit id? LString args; args.Printf("-P show %s", Filter); ParseParams *params = new ParseParams; params->Callback = [this, Filter=LString(Filter)](auto code, auto str) { ClearLog(); if (code == 0 && str.Find(Filter) >= 0) { // Found the commit... d->Commits->Empty(); CurrentCommit.Empty(); ParseLog(code, str, NULL); d->Commits->Insert(Log); } else { // Not a commit ref...? LString args; args.Printf("log --grep \"%s\"", Filter.Get()); IsLogging = StartCmd(args, &VcFolder::ParseLog); } }; StartCmd(args, NULL, params); break; } default: { NoImplementation(_FL); break; } } } void VcFolder::LogFile(const char *uri) { LString Args; if (IsLogging) { d->Log->Print("%s:%i - already logging.\n", _FL); return; } const char *Page = ""; switch (GetType()) { case VcGit: Page = "-P "; // fall through case VcSvn: case VcHg: { FileToSelect = GetFilePart(uri); if (IsLocal() && !LFileExists(FileToSelect)) { LFile::Path Abs(LocalPath()); Abs += FileToSelect; if (Abs.Exists()) FileToSelect = Abs; } ParseParams *Params = new ParseParams(uri); Args.Printf("%slog \"%s\"", Page, FileToSelect.Get()); IsLogging = StartCmd(Args, &VcFolder::ParseLog, Params, LogNormal); break; } default: NoImplementation(_FL); break; } } VcLeaf *VcFolder::FindLeaf(const char *Path, bool OpenTree) { VcLeaf *r = NULL; if (OpenTree) DoExpand(); for (auto n = GetChild(); !r && n; n = n->GetNext()) { auto l = dynamic_cast(n); if (l) r = l->FindLeaf(Path, OpenTree); } return r; } bool VcFolder::ParseLog(int Result, LString s, ParseParams *Params) { int Skipped = 0, Errors = 0; bool LoggingFile = Params ? Params->Str != NULL : false; VcLeaf *File = LoggingFile ? FindLeaf(Params->Str, true) : NULL; // This may be NULL even if we are logging a file... LArray *Out, BrowseLog; if (File) Out = &File->Log; else if (LoggingFile) Out = &BrowseLog; else Out = &Log; LHashTbl, VcCommit*> Map; for (auto pc: *Out) Map.Add(pc->GetRev(), pc); if (File) { for (auto Leaf = File; Leaf; Leaf = dynamic_cast(Leaf->GetParent())) Leaf->OnExpand(true); File->Select(true); File->ScrollTo(); } switch (GetType()) { case VcGit: { LString::Array c; c.SetFixedLength(false); char *prev = s.Get(); #if 0 LFile::Path outPath("~/code/dump.txt"); LFile out(outPath.Absolute(), O_WRITE); out.Write(s); #endif if (!s) { OnCmdError(s, "No output from command."); return false; } char *i = s.Get(); while (*i) { if (!strnicmp(i, "commit ", 7)) { if (i > prev) { c.New().Set(prev, i - prev); // LgiTrace("commit=%i\n", (int)(i - prev)); } prev = i; } while (*i) { if (*i++ == '\n') break; } } if (prev && i > prev) { // Last one... c.New().Set(prev, i - prev); } for (auto txt: c) { LAutoPtr Rev(new VcCommit(d, this)); if (Rev->GitParse(txt, false)) { if (!Map.Find(Rev->GetRev())) Out->Add(Rev.Release()); else Skipped++; } else { LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, txt.Get()); Errors++; } } Out->Sort(CommitDateCmp); break; } case VcSvn: { LString::Array c = s.Split("------------------------------------------------------------------------"); for (unsigned i=0; i Rev(new VcCommit(d, this)); LString Raw = c[i].Strip(); if (Rev->SvnParse(Raw)) { if (File || !Map.Find(Rev->GetRev())) Out->Add(Rev.Release()); else Skipped++; } else if (Raw) { OnCmdError(Raw, "ParseLog Failed"); Errors++; } } Out->Sort(CommitRevCmp); break; } case VcHg: { LString::Array c = s.Split("\n\n"); LHashTbl, VcCommit*> Idx; for (auto &Commit: c) { LAutoPtr Rev(new VcCommit(d, this)); if (Rev->HgParse(Commit)) { auto Existing = File ? NULL : Map.Find(Rev->GetRev()); if (!Existing) Out->Add(Existing = Rev.Release()); if (Existing->GetIndex() >= 0) Idx.Add(Existing->GetIndex(), Existing); } } if (!File) { // Patch all the trivial parents... for (auto c: Log) { if (c->GetParents()->Length() > 0) continue; auto CIdx = c->GetIndex(); if (CIdx <= 0) continue; auto Par = Idx.Find(CIdx - 1); if (Par) c->GetParents()->Add(Par->GetRev()); } } Out->Sort(CommitIndexCmp); if (!File) LinkParents(); d->Resort = 1; break; } case VcCvs: { if (Result) { OnCmdError(s, "Cvs command failed."); break; } LHashTbl, VcCommit*> Map; LString::Array c = s.Split("============================================================================="); for (auto &Commit: c) { if (Commit.Strip().Length()) { LString Head, File; LString::Array Versions = Commit.Split("----------------------------"); LString::Array Lines = Versions[0].SplitDelimit("\r\n"); for (auto &Line: Lines) { LString::Array p = Line.Split(":", 1); if (p.Length() == 2) { // LgiTrace("Line: %s\n", Line->Get()); LString Var = p[0].Strip().Lower(); LString Val = p[1].Strip(); if (Var.Equals("branch")) { if (Val.Length()) Branches.Add(Val, new VcBranch(Val)); } else if (Var.Equals("head")) { Head = Val; } else if (Var.Equals("rcs file")) { LString::Array f = Val.SplitDelimit(","); File = f.First(); } } } // LgiTrace("%s\n", Commit->Get()); for (unsigned i=1; i= 3) { LString Ver = Lines[0].Split(" ").Last(); LString::Array a = Lines[1].SplitDelimit(";"); LString Date = a[0].Split(":", 1).Last().Strip(); LString Author = a[1].Split(":", 1).Last().Strip(); LString Id = a[2].Split(":", 1).Last().Strip(); LString Msg = Lines[2]; LDateTime Dt; if (Dt.Parse(Date)) { LTimeStamp Ts; if (Dt.Get(Ts)) { VcCommit *Cc = Map.Find(Ts.Get()); if (!Cc) { Map.Add(Ts.Get(), Cc = new VcCommit(d, this)); Out->Add(Cc); Cc->CvsParse(Dt, Author, Msg); } Cc->Files.Add(File.Get()); } else LAssert(!"NO ts for date."); } else LAssert(!"Date parsing failed."); } } } } break; } default: LAssert(!"Impl me."); break; } if (File) { File->ShowLog(); } else if (LoggingFile) { if (auto ui = new BrowseUi(BrowseUi::TLog, d, this, Params->Str)) ui->ParseLog(BrowseLog, s); } // LgiTrace("%s:%i - ParseLog: Skip=%i, Error=%i\n", _FL, Skipped, Errors); IsLogging = false; return !Result; } void VcFolder::LinkParents() { #if PROFILE_FN LProfile Prof("LinkParents"); #endif LHashTbl,VcCommit*> Map; // Index all the commits int i = 0; for (auto c:Log) { c->Idx = i++; c->NodeIdx = -1; Map.Add(c->GetRev(), c); } // Create all the edges... PROF("Create edges."); for (auto c:Log) { auto *Par = c->GetParents(); for (auto &pRev : *Par) { auto *p = Map.Find(pRev); if (p) new VcEdge(p, c); #if 0 else return; #endif } } // Map the edges to positions PROF("Map edges."); typedef LArray EdgeArr; LArray Active; for (auto c:Log) { for (unsigned i=0; c->NodeIdx<0 && iParent == c) { c->NodeIdx = i; break; } } } // Add starting edges to active set for (auto e:c->Edges) { if (e->Child == c) { if (c->NodeIdx < 0) c->NodeIdx = (int)Active.Length(); e->Idx = c->NodeIdx; c->Pos.Add(e, e->Idx); Active[e->Idx].Add(e); } } // Now for all active edges... assign positions for (unsigned i=0; iLength(); n++) { LAssert(Active.PtrCheck(Edges)); VcEdge *e = (*Edges)[n]; if (c == e->Child || c == e->Parent) { LAssert(c->NodeIdx >= 0); c->Pos.Add(e, c->NodeIdx); } else { // May need to untangle edges with different parents here bool Diff = false; for (auto edge: *Edges) { if (edge != e && edge->Child != c && edge->Parent != e->Parent) { Diff = true; break; } } if (Diff) { int NewIndex = -1; // Look through existing indexes for a parent match for (unsigned ii=0; iiParent? bool Match = true; for (auto ee: Active[ii]) { if (ee->Parent != e->Parent) { Match = false; break; } } if (Match) NewIndex = ii; } if (NewIndex < 0) // Create new index for this parent NewIndex = (int)Active.Length(); Edges->Delete(e); auto &NewEdges = Active[NewIndex]; NewEdges.Add(e); Edges = &Active[i]; // The 'Add' above can invalidate the object 'Edges' refers to e->Idx = NewIndex; c->Pos.Add(e, NewIndex); n--; } else { LAssert(e->Idx == i); c->Pos.Add(e, i); } } } } // Process terminating edges for (auto e: c->Edges) { if (e->Parent == c) { if (e->Idx < 0) { // This happens with out of order commits..? continue; } int i = e->Idx; if (c->NodeIdx < 0) c->NodeIdx = i; if (Active[i].HasItem(e)) Active[i].Delete(e); else LgiTrace("%s:%i - Warning: Active doesn't have 'e'.\n", _FL); } } // Collapse any empty active columns for (unsigned i=0; iIdx > 0); edge->Idx--; c->Pos.Add(edge, edge->Idx); } } i--; } } } } void VcFolder::UpdateBranchUi() { auto w = d->Wnd(); DropDownBtn *dd; if (w->GetViewById(IDC_BRANCH_DROPDOWN, dd)) { LString::Array a; for (auto b: Branches) a.Add(b.key); dd->SetList(IDC_BRANCH, a); } LViewI *b; if (Branches.Length() > 0 && w->GetViewById(IDC_BRANCH, b)) { if (CurrentBranch) { b->Name(CurrentBranch); } else { auto it = Branches.begin(); if (it != Branches.end()) b->Name((*it).key); } } LCombo *Cbo; if (w->GetViewById(IDC_BRANCHES, Cbo)) { Cbo->Empty(); int64 select = -1; for (auto b: Branches) { if (CurrentBranch && CurrentBranch == b.key) select = Cbo->Length(); Cbo->Insert(b.key); } if (select >= 0) Cbo->Value(select); Cbo->SendNotify(LNotifyTableLayoutRefresh); // LgiTrace("%s:%i - Branches len=%i->%i\n", _FL, (int)Branches.Length(), (int)Cbo->Length()); } } VcFile *AppPriv::FindFile(const char *Path) { if (!Path) return NULL; LArray files; if (Files->GetAll(files)) { LString p = Path; p = p.Replace(DIR_STR, "/"); for (auto f : files) { auto Fn = f->GetFileName(); if (p.Equals(Fn)) return f; } } return NULL; } VcFile *VcFolder::FindFile(const char *Path) { return d->FindFile(Path); } void VcFolder::NoImplementation(const char* file, int line) { LString s; s.Printf("%s, uri=%s, type=%s (%s:%i)", LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE), Uri.ToString().Get(), toString(GetType()), file, line); OnCmdError(LString(), s); } void VcFolder::OnCmdError(LString Output, const char *Msg) { if (!CmdErrors) { if (Output.Length()) d->Log->Write(Output, Output.Length()); auto vc_name = GetVcName(); if (vc_name) { LString::Array a = GetProgramsInPath(GetVcName()); d->Log->Print("'%s' executables in the path:\n", GetVcName()); for (auto Bin : a) d->Log->Print(" %s\n", Bin.Get()); } else if (Msg) { d->Log->Print("%s\n", Msg); } } CmdErrors++; d->Tabs->Value(1); GetCss(true)->Color(LColour::Red); Update(); } void VcFolder::ClearError() { GetCss(true)->Color(LCss::ColorInherit); } bool VcFolder::ParseInfo(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcGit: case VcHg: { auto p = s.Strip().SplitDelimit(); CurrentCommit = p[0].Strip(" \t\r\n+"); if (p.Length() > 1) CurrentCommitIdx = p[1].Int(); else CurrentCommitIdx = -1; if (Params && Params->Str.Equals("CountToTip")) CountToTip(); break; } case VcSvn: { if (s.Find("client is too old") >= 0) { OnCmdError(s, "Client too old"); break; } LString::Array c = s.Split("\n"); for (unsigned i=0; iStr.Equals("Branch")) SetCurrentBranch(NewRev); else CurrentCommit = NewRev; } NewRev.Empty(); IsUpdate = false; return true; } bool VcFolder::ParseWorking(int Result, LString s, ParseParams *Params) { IsListingWorking = false; switch (GetType()) { case VcSvn: case VcHg: { ParseParams Local; if (!Params) Params = &Local; Params->IsWorking = true; ParseStatus(Result, s, Params); break; } case VcCvs: { bool Untracked = d->IsMenuChecked(IDM_UNTRACKED); if (Untracked) { auto Lines = s.SplitDelimit("\n"); for (auto Ln: Lines) { auto p = Ln.SplitDelimit(" \t", 1); if (p.Length() > 1) { auto f = new VcFile(d, this, LString(), true); f->SetText(p[0], COL_STATE); f->SetText(p[1], COL_FILENAME); f->GetStatus(); d->Files->Insert(f); } } } // else fall thru } default: { ParseDiffs(s, LString(), true); break; } } FilterCurrentFiles(); d->Files->ResizeColumnsToContent(); if (GetType() == VcSvn) { Unpushed = d->Files->Length() > 0 ? 1 : 0; Update(); } return false; } void VcFolder::DiffRange(const char *FromRev, const char *ToRev) { if (!FromRev || !ToRev) return; switch (GetType()) { case VcSvn: { ParseParams *p = new ParseParams; p->IsWorking = false; p->Str = LString(FromRev) + ":" + ToRev; LString a; a.Printf("diff -r%s:%s", FromRev, ToRev); StartCmd(a, &VcFolder::ParseDiff, p); break; } case VcGit: { ParseParams *p = new ParseParams; p->IsWorking = false; p->Str = LString(FromRev) + ":" + ToRev; LString a; a.Printf("-P diff %s..%s", FromRev, ToRev); StartCmd(a, &VcFolder::ParseDiff, p); break; } case VcCvs: case VcHg: default: LAssert(!"Impl me."); break; } } bool VcFolder::ParseDiff(int Result, LString s, ParseParams *Params) { if (Params) ParseDiffs(s, Params->Str, Params->IsWorking); else ParseDiffs(s, LString(), true); return false; } void VcFolder::Diff(VcFile *file) { auto Fn = file->GetFileName(); if (!Fn || !Stricmp(Fn, ".") || !Stricmp(Fn, "..")) return; const char *Prefix = ""; switch (GetType()) { case VcGit: Prefix = "-P "; // fall through case VcHg: { LString a; auto rev = file->GetRevision(); if (rev) a.Printf("%sdiff %s \"%s\"", Prefix, rev, Fn); else a.Printf("%sdiff \"%s\"", Prefix, Fn); StartCmd(a, &VcFolder::ParseDiff); break; } case VcSvn: { LString a; if (file->GetRevision()) a.Printf("diff -r %s \"%s\"", file->GetRevision(), Fn); else a.Printf("diff \"%s\"", Fn); StartCmd(a, &VcFolder::ParseDiff); break; } case VcCvs: break; default: LAssert(!"Impl me."); break; } } void VcFolder::InsertFiles(List &files) { d->Files->Insert(files); if (FileToSelect) { LListItem *scroll = NULL; for (auto f: files) { // Convert to an absolute path: bool match = false; auto relPath = f->GetText(COL_FILENAME); if (IsLocal()) { LFile::Path p(LocalPath()); p += relPath; match = p.GetFull().Equals(FileToSelect); } else { match = !Stricmp(FileToSelect.Get(), relPath); } f->Select(match); if (match) scroll = f; } if (scroll) scroll->ScrollTo(); } } bool VcFolder::ParseDiffs(LString s, LString Rev, bool IsWorking) { LAssert(IsWorking || Rev.Get() != NULL); switch (GetType()) { case VcGit: { List Files; LString::Array a = s.Split("\n"); LString Diff; VcFile *f = NULL; for (unsigned i=0; iSetDiff(Diff); Diff.Empty(); auto Bits = a[i].SplitDelimit(); LString Fn, State = "M"; if (Bits[1].Equals("--cc")) { Fn = Bits.Last(); State = "C"; } else Fn = Bits.Last()(2,-1); // LgiTrace("%s\n", a[i].Get()); f = FindFile(Fn); if (!f) f = new VcFile(d, this, Rev, IsWorking); f->SetText(State, COL_STATE); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); f->GetStatus(); Files.Insert(f); } else if (!_strnicmp(Ln, "new file", 8)) { if (f) f->SetText("A", COL_STATE); } else if (!_strnicmp(Ln, "deleted file", 12)) { if (f) f->SetText("D", COL_STATE); } else if (!_strnicmp(Ln, "index", 5) || !_strnicmp(Ln, "commit", 6) || !_strnicmp(Ln, "Author:", 7) || !_strnicmp(Ln, "Date:", 5) || !_strnicmp(Ln, "+++", 3) || !_strnicmp(Ln, "---", 3)) { // Ignore } else { if (Diff) Diff += "\n"; Diff += a[i]; } } if (f && Diff) { f->SetDiff(Diff); Diff.Empty(); } InsertFiles(Files); break; } case VcHg: { LString Sep("\n"); LString::Array a = s.Split(Sep); LString::Array Diffs; VcFile *f = NULL; List Files; LProgressDlg Prog(GetTree(), 1000); Prog.SetDescription("Reading diff lines..."); Prog.SetRange(a.Length()); // Prog.SetYieldTime(300); for (unsigned i=0; iSetDiff(Sep.Join(Diffs)); Diffs.Empty(); auto MainParts = a[i].Split(" -r "); auto FileParts = MainParts.Last().Split(" ",1); LString Fn = FileParts.Last(); f = FindFile(Fn); if (!f) f = new VcFile(d, this, Rev, IsWorking); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); // f->SetText(Status, COL_STATE); Files.Insert(f); } else if (!_strnicmp(Ln, "index", 5) || !_strnicmp(Ln, "commit", 6) || !_strnicmp(Ln, "Author:", 7) || !_strnicmp(Ln, "Date:", 5) || !_strnicmp(Ln, "+++", 3) || !_strnicmp(Ln, "---", 3)) { // Ignore } else { Diffs.Add(a[i]); } Prog.Value(i); if (Prog.IsCancelled()) break; } if (f && Diffs.Length()) { f->SetDiff(Sep.Join(Diffs)); Diffs.Empty(); } InsertFiles(Files); break; } case VcSvn: { List Files; LString::Array a = s.Replace("\r").Split("\n"); LString Diff; VcFile *f = NULL; bool InPreamble = false; bool InDiff = false; for (unsigned i=0; iSetDiff(Diff); f->Select(false); } Diff.Empty(); InDiff = false; InPreamble = false; LString Fn = a[i].Split(":", 1).Last().Strip(); f = FindFile(Fn); if (!f) f = new VcFile(d, this, Rev, IsWorking); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); f->SetText("M", COL_STATE); f->GetStatus(); Files.Insert(f); } else if (!_strnicmp(Ln, "------", 6)) { InPreamble = !InPreamble; } else if (!_strnicmp(Ln, "======", 6)) { InPreamble = false; InDiff = true; } else if (InDiff) { if (!strncmp(Ln, "--- ", 4) || !strncmp(Ln, "+++ ", 4)) { } else { if (Diff) Diff += "\n"; Diff += a[i]; } } } InsertFiles(Files); if (f && Diff) { f->SetDiff(Diff); Diff.Empty(); } break; } case VcCvs: { break; } default: { LAssert(!"Impl me."); break; } } FilterCurrentFiles(); return true; } bool VcFolder::ParseFiles(int Result, LString s, ParseParams *Params) { d->ClearFiles(); ParseDiffs(s, Params->Str, false); IsFilesCmd = false; FilterCurrentFiles(); return false; } #if HAS_LIBSSH void VcFolder::OnSshCmd(SshParams *p) { if (!p || !p->f) { LAssert(!"Param error."); return; } LString s = p->Output; int Result = p->ExitCode; if (Result == ErrSubProcessFailed) { CmdErrors++; } else if (p->Parser) { bool Reselect = CALL_MEMBER_FN(*this, p->Parser)(Result, s, p->Params); if (Reselect) { if (LTreeItem::Select()) Select(true); } } if (p->Params && p->Params->Callback) { p->Params->Callback(Result, s); } } #endif void VcFolder::OnPulse() { bool Reselect = false, CmdsChanged = false; static bool Processing = false; if (!Processing) { Processing = true; // Lock out processing, if it puts up a dialog or something... // bad things happen if we try and re-process something. // printf("Cmds.Len=%i\n", (int)Cmds.Length()); for (unsigned i=0; iRd->GetState()); if (c->Rd->GetState() == LThread::THREAD_INIT) { if (CmdActiveThreads < CmdMaxThreads) { c->Rd->Run(); CmdActiveThreads++; // printf("CmdActiveThreads++ = %i\n", CmdActiveThreads); } // else printf("Too many active threads.\n"); } else if (c->Rd->IsExited()) { CmdActiveThreads--; // printf("CmdActiveThreads-- = %i\n", CmdActiveThreads); LString s = c->GetBuf(); int Result = c->Rd->ExitCode(); if (Result == ErrSubProcessFailed) { if (!CmdErrors) d->Log->Print("Error: Can't run '%s'\n", GetVcName()); CmdErrors++; } else if (c->PostOp) { if (s.Length() == 18 && - s.Equals("GSUBPROCESS_ERROR\n")) + s.Equals("LSUBPROCESS_ERROR\n")) { OnCmdError(s, "Sub process failed."); } else { Reselect |= CALL_MEMBER_FN(*this, c->PostOp)(Result, s, c->Params); } } if (c->Params && c->Params->Callback) { c->Params->Callback(Result, s); } Cmds.DeleteAt(i--, true); delete c; CmdsChanged = true; } // else printf("Not exited.\n"); } Processing = false; } if (Reselect) { if (LTreeItem::Select()) Select(true); } if (CmdsChanged) { Update(); } if (CmdErrors) { d->Tabs->Value(1); CmdErrors = false; } } void VcFolder::OnRemove() { LXmlTag *t = d->Opts.LockTag(OPT_Folders, _FL); if (t) { Uncommit.Reset(); if (LTreeItem::Select()) { d->Files->Empty(); d->Commits->RemoveAll(); } bool Found = false; auto u = Uri.ToString(); for (auto c: t->Children) { if (!c->IsTag(OPT_Folder)) printf("%s:%i - Wrong tag: %s, %s\n", _FL, c->GetTag(), OPT_Folder); else if (!c->GetContent()) printf("%s:%i - No content.\n", _FL); else { auto Content = c->GetContent(); if (!_stricmp(Content, u)) { c->RemoveTag(); delete c; Found = true; break; } } } LAssert(Found); d->Opts.Unlock(); } } void VcFolder::Empty() { Type = VcNone; IsCommit = false; IsLogging = false; IsUpdate = false; IsFilesCmd = false; CommitListDirty = false; IsUpdatingCounts = false; IsBranches = StatusNone; IsIdent = StatusNone; Unpushed = Unpulled = -1; CmdErrors = 0; CurrentCommitIdx = -1; CurrentCommit.Empty(); RepoUrl.Empty(); VcCmd.Empty(); Uncommit.Reset(); Log.DeleteObjects(); d->Commits->Empty(); d->Files->Empty(); if (!Uri.IsFile()) GetCss(true)->Color(LColour::Blue); } void VcFolder::OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { LSubMenu s; s.AppendItem("Browse To", IDM_BROWSE_FOLDER, Uri.IsFile()); s.AppendItem( #ifdef WINDOWS "Command Prompt At", #else "Terminal At", #endif IDM_TERMINAL, Uri.IsFile()); s.AppendItem("Clean", IDM_CLEAN); s.AppendSeparator(); s.AppendItem("Pull", IDM_PULL); s.AppendItem("Status", IDM_STATUS); s.AppendItem("Push", IDM_PUSH); s.AppendItem("Update Subs", IDM_UPDATE_SUBS, GetType() == VcGit); s.AppendSeparator(); s.AppendItem("Remove", IDM_REMOVE); s.AppendItem("Remote URL", IDM_REMOTE_URL); if (!Uri.IsFile()) { s.AppendSeparator(); s.AppendItem("Edit Location", IDM_EDIT); } int Cmd = s.Float(GetTree(), m); switch (Cmd) { case IDM_BROWSE_FOLDER: { LBrowseToFile(LocalPath()); break; } case IDM_TERMINAL: { TerminalAt(LocalPath()); break; } case IDM_CLEAN: { Clean(); break; } case IDM_PULL: { Pull(); break; } case IDM_STATUS: { FolderStatus(); break; } case IDM_PUSH: { Push(); break; } case IDM_UPDATE_SUBS: { UpdateSubs(); break; } case IDM_REMOVE: { OnRemove(); delete this; break; } case IDM_EDIT: { auto Dlg = new LInput(GetTree(), Uri.ToString(), "URI:", "Remote Folder Location"); Dlg->DoModal([this, Dlg](auto dlg, auto ctrlId) { if (ctrlId) { Uri.Set(Dlg->GetStr()); Empty(); Select(true); } delete dlg; }); break; } case IDM_REMOTE_URL: { GetRemoteUrl([this](auto code, auto str) { LString Url = str.Strip(); if (Url) { auto a = new LAlert(GetTree(), "Remote Url", Url, "Copy", "Ok"); a->DoModal([this, Url](auto dlg, auto code) { if (code == 1) { LClipBoard c(GetTree()); c.Text(Url); } delete dlg; }); } }); break; } default: break; } } } LString &VcFolder::GetCurrentBranch() { return CurrentBranch; } void VcFolder::SetCurrentBranch(LString name) { if (CurrentBranch != name) { CurrentBranch = name; UpdateBranchUi(); } } void VcFolder::Checkout(const char *Rev, bool isBranch) { if (!Rev || IsUpdate) return; LString Args; LAutoPtr params(new ParseParams(isBranch ? "Branch" : "Rev")); NewRev = Rev; switch (GetType()) { case VcGit: Args.Printf("checkout %s", Rev); IsUpdate = StartCmd(Args, &VcFolder::ParseCheckout, params.Release(), LogNormal); break; case VcSvn: Args.Printf("up -r %s", Rev); IsUpdate = StartCmd(Args, &VcFolder::ParseCheckout, params.Release(), LogNormal); break; case VcHg: Args.Printf("update -r %s", Rev); IsUpdate = StartCmd(Args, &VcFolder::ParseCheckout, params.Release(), LogNormal); break; default: { NoImplementation(_FL); break; } } } /////////////////////////////////////////////////////////////////////////////////////// int FolderCompare(LTreeItem *a, LTreeItem *b, NativeInt UserData) { VcLeaf *A = dynamic_cast(a); VcLeaf *B = dynamic_cast(b); if (!A || !B) return 0; return A->Compare(B); } struct SshFindEntry { LString Flags, Name, User, Group; uint64_t Size; LDateTime Modified, Access; SshFindEntry &operator =(const LString &s) { auto p = s.SplitDelimit("/"); if (p.Length() == 7) { Flags = p[0]; Group = p[1]; User = p[2]; Access.Set((uint64_t) p[3].Int()); Modified.Set((uint64_t) p[4].Int()); Size = p[5].Int(); Name = p[6]; } return *this; } bool IsDir() { return Flags(0) == 'd'; } bool IsHidden() { return Name(0) == '.'; } const char *GetName() { return Name; } static int Compare(SshFindEntry *a, SshFindEntry *b) { return Stricmp(a->Name.Get(), b->Name.Get()); } }; bool VcFolder::ParseRemoteFind(int Result, LString s, ParseParams *Params) { if (!Params || !s) return false; auto Parent = Params->Leaf ? static_cast(Params->Leaf) : static_cast(this); LUri u(Params->Str); auto Lines = s.SplitDelimit("\r\n"); LArray Entries; for (size_t i=1; iStr, Dir.GetName(), true); } } else if (!Dir.IsHidden()) { char *Ext = LGetExtension(Dir.GetName()); if (!Ext) continue; if (!stricmp(Ext, "c") || !stricmp(Ext, "cpp") || !stricmp(Ext, "h")) { LUri Path = u; Path += Dir.GetName(); new VcLeaf(this, Parent, Params->Str, Dir.GetName(), false); } } } return false; } void VcFolder::ReadDir(LTreeItem *Parent, const char *ReadUri) { LUri u(ReadUri); if (u.IsFile()) { // Read child items LDirectory Dir; for (int b = Dir.First(u.LocalPath()); b; b = Dir.Next()) { auto name = Dir.GetName(); if (Dir.IsHidden()) continue; LUri Path = u; Path += name; new VcLeaf(this, Parent, u.ToString(), name, Dir.IsDir()); } } #if HAS_LIBSSH else { auto c = d->GetConnection(ReadUri); if (!c) return; LString Path = u.sPath(Uri.sPath.Length(), -1).LStrip("/"); LString Args; Args.Printf("\"%s\" -maxdepth 1 -printf \"%%M/%%g/%%u/%%A@/%%T@/%%s/%%P\n\"", Path ? Path.Get() : "."); auto *Params = new ParseParams(ReadUri); Params->Leaf = dynamic_cast(Parent); c->Command(this, "find", Args, &VcFolder::ParseRemoteFind, Params, SshConnection::LogNone); return; } #endif Parent->Sort(FolderCompare); } void VcFolder::OnVcsType(LString errorMsg) { if (!d) { LAssert(!"No priv instance"); return; } #if HAS_LIBSSH auto c = d->GetConnection(Uri.ToString(), false); if (c) { auto NewType = c->Types.Find(Uri.sPath); if (NewType && NewType != Type) { if (NewType == VcError) { OnCmdError(LString(), errorMsg); } else { Type = NewType; ClearError(); Update(); if (LTreeItem::Select()) Select(true); for (auto &e: OnVcsTypeEvents) e(); OnVcsTypeEvents.Empty(); } } } #endif } void VcFolder::DoExpand() { if (Tmp) { Tmp->Remove(); DeleteObj(Tmp); ReadDir(this, Uri.ToString()); } } void VcFolder::OnExpand(bool b) { if (b) DoExpand(); } void VcFolder::ListCommit(VcCommit *c) { if (!IsFilesCmd) { LString Args; switch (GetType()) { case VcGit: Args.Printf("-P show %s^..%s", c->GetRev(), c->GetRev()); IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev())); break; case VcSvn: Args.Printf("log --verbose --diff -r %s", c->GetRev()); IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev())); break; case VcCvs: { d->ClearFiles(); for (unsigned i=0; iFiles.Length(); i++) { VcFile *f = new VcFile(d, this, c->GetRev(), false); if (f) { f->SetText(c->Files[i], COL_FILENAME); d->Files->Insert(f); } } FilterCurrentFiles(); break; } case VcHg: { Args.Printf("diff --change %s", c->GetRev()); IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev())); break; } default: LAssert(!"Impl me."); break; } if (IsFilesCmd) d->ClearFiles(); } } LString ConvertUPlus(LString s) { LArray c; LUtf8Ptr p(s); int32 ch; while ((ch = p)) { if (ch == '{') { auto n = p.GetPtr(); if (n[1] == 'U' && n[2] == '+') { // Convert unicode code point p += 3; ch = (int32)htoi(p.GetPtr()); c.Add(ch); while ((ch = p) != '}') p++; } else c.Add(ch); } else c.Add(ch); p++; } c.Add(0); #ifdef LINUX return LString((char16*)c.AddressOf()); #else return LString(c.AddressOf()); #endif } bool VcFolder::ParseStatus(int Result, LString s, ParseParams *Params) { bool ShowUntracked = d->Wnd()->GetCtrlValue(IDC_UNTRACKED) != 0; bool IsWorking = Params ? Params->IsWorking : false; List Ins; switch (GetType()) { case VcCvs: { LHashTbl,VcFile*> Map; for (auto i: *d->Files) { VcFile *f = dynamic_cast(i); if (f) Map.Add(f->GetText(COL_FILENAME), f); } #if 0 LFile Tmp("C:\\tmp\\output.txt", O_WRITE); Tmp.Write(s); Tmp.Close(); #endif LString::Array a = s.Split("==================================================================="); for (auto i : a) { LString::Array Lines = i.SplitDelimit("\r\n"); if (Lines.Length() == 0) continue; LString f = Lines[0].Strip(); if (f.Find("File:") == 0) { LString::Array Parts = f.SplitDelimit("\t"); LString File = Parts[0].Split(": ").Last().Strip(); LString Status = Parts[1].Split(": ").Last(); LString WorkingRev; for (auto l : Lines) { LString::Array p = l.Strip().Split(":", 1); if (p.Length() > 1 && p[0].Strip().Equals("Working revision")) { WorkingRev = p[1].Strip(); } } VcFile *f = Map.Find(File); if (!f) { if ((f = new VcFile(d, this, WorkingRev, IsWorking))) Ins.Insert(f); } if (f) { f->SetText(Status, COL_STATE); f->SetText(File, COL_FILENAME); f->Update(); } } else if (f(0) == '?' && ShowUntracked) { LString File = f(2, -1); VcFile *f = Map.Find(File); if (!f) { if ((f = new VcFile(d, this, LString(), IsWorking))) Ins.Insert(f); } if (f) { f->SetText("?", COL_STATE); f->SetText(File, COL_FILENAME); f->Update(); } } } for (auto i: *d->Files) { VcFile *f = dynamic_cast(i); if (f) { if (f->GetStatus() == VcFile::SUnknown) f->SetStatus(VcFile::SUntracked); } } break; } case VcGit: { auto Lines = s.SplitDelimit("\r\n"); int Fmt = ToolVersion[VcGit] >= Ver2Int("2.8.0") ? 2 : 1; for (auto Ln : Lines) { auto Type = Ln(0); if (Ln.Lower().Find("error:") >= 0) { } else if (Ln.Find("usage: git") >= 0) { // It's probably complaining about the --porcelain=2 parameter OnCmdError(s, "Args error"); } else if (Type != '?') { VcFile *f = NULL; if (Fmt == 2) { LString::Array p = Ln.SplitDelimit(" ", 8); if (p.Length() < 7) d->Log->Print("%s:%i - Error: not enough tokens: '%s'\n", _FL, Ln.Get()); else { auto path = p[6]; f = new VcFile(d, this, path, IsWorking); auto state = p[1].Strip("."); auto pos = p[1].Find(state); d->Log->Print("%s state='%s' pos=%i\n", path.Get(), state.Get(), (int)pos); f->SetText(state, COL_STATE); f->SetText(p.Last(), COL_FILENAME); f->SetStaged(pos == 0); } } else if (Fmt == 1) { LString::Array p = Ln.SplitDelimit(" "); f = new VcFile(d, this, LString(), IsWorking); f->SetText(p[0], COL_STATE); f->SetText(p.Last(), COL_FILENAME); } if (f) Ins.Insert(f); } else if (ShowUntracked) { VcFile *f = new VcFile(d, this, LString(), IsWorking); f->SetText("?", COL_STATE); f->SetText(Ln(2,-1), COL_FILENAME); Ins.Insert(f); } } break; } case VcHg: case VcSvn: { if (s.Find("failed to import") >= 0) { OnCmdError(s, "Tool error."); return false; } LString::Array Lines = s.SplitDelimit("\r\n"); for (auto Ln : Lines) { char Type = Ln(0); if (Ln.Lower().Find("error:") >= 0) { } else if (Ln.Find("client is too old") >= 0) { OnCmdError(s, "Client too old."); return false; } else if (Strchr(" \t", Type) || Ln.Find("Summary of conflicts") >= 0) { // Ignore } else if (Type != '?') { LString::Array p = Ln.SplitDelimit(" ", 1); if (p.Length() == 2) { LString File; if (GetType() == VcSvn) File = ConvertUPlus(p.Last()); else File = p.Last(); if (GetType() == VcSvn && File.Find("+ ") == 0) { File = File(5, -1); } VcFile *f = new VcFile(d, this, LString(), IsWorking); f->SetText(p[0], COL_STATE); f->SetText(File.Replace("\\","/"), COL_FILENAME); f->GetStatus(); Ins.Insert(f); } else LAssert(!"What happen?"); } else if (ShowUntracked) { VcFile *f = new VcFile(d, this, LString(), IsWorking); f->SetText("?", COL_STATE); f->SetText(Ln(2,-1), COL_FILENAME); Ins.Insert(f); } } break; } default: { LAssert(!"Impl me."); break; } } if ((Unpushed = Ins.Length() > 0)) { if (CmdErrors == 0) GetCss(true)->Color(LColour(255, 128, 0)); } else if (Unpulled == 0) { GetCss(true)->Color(LCss::ColorInherit); } Update(); if (LTreeItem::Select()) { d->Files->Insert(Ins); FilterCurrentFiles(); } else { Ins.DeleteObjects(); } if (Params && Params->Leaf) Params->Leaf->AfterBrowse(); return false; // Don't refresh list } // Clone/checkout any sub-repositries. bool VcFolder::UpdateSubs() { LString Arg; switch (GetType()) { default: case VcSvn: case VcHg: case VcCvs: return false; case VcGit: Arg = "submodule update --init --recursive"; break; } return StartCmd(Arg, &VcFolder::ParseUpdateSubs, NULL, LogNormal); } bool VcFolder::ParseUpdateSubs(int Result, LString s, ParseParams *Params) { switch (GetType()) { default: case VcSvn: case VcHg: case VcCvs: return false; case VcGit: break; } return false; } void VcFolder::FolderStatus(const char *uri, VcLeaf *Notify) { LUri Uri(uri); if (Uri.IsFile() && Uri.sPath) { LFile::Path p(Uri.sPath(1,-1)); if (!p.IsFolder()) { LAssert(!"Needs to be a folder."); return; } } if (LTreeItem::Select()) d->ClearFiles(); LString Arg; switch (GetType()) { case VcSvn: case VcHg: Arg = "status"; break; case VcCvs: Arg = "status -l"; break; case VcGit: if (!ToolVersion[VcGit]) LAssert(!"Where is the version?"); // What version did =2 become available? It's definitely not in v2.5.4 // Not in v2.7.4 either... if (ToolVersion[VcGit] >= Ver2Int("2.8.0")) Arg = "-P status --porcelain=2"; else Arg = "-P status --porcelain"; break; default: return; } ParseParams *p = new ParseParams; if (uri && Notify) { p->AltInitPath = uri; p->Leaf = Notify; } else { p->IsWorking = true; } StartCmd(Arg, &VcFolder::ParseStatus, p); switch (GetType()) { case VcHg: CountToTip(); break; default: break; } } void VcFolder::CountToTip() { // if (Path.Equals("C:\\Users\\matthew\\Code\\Lgi\\trunk")) { // LgiTrace("%s: CountToTip, br=%s, idx=%i\n", Path.Get(), CurrentBranch.Get(), (int)CurrentCommitIdx); if (!CurrentBranch) GetBranches(new ParseParams("CountToTip")); else if (CurrentCommitIdx < 0) GetCurrentRevision(new ParseParams("CountToTip")); else { LString Arg; Arg.Printf("id -n -r %s", CurrentBranch.Get()); StartCmd(Arg, &VcFolder::ParseCountToTip); } } } bool VcFolder::ParseCountToTip(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcHg: if (CurrentCommitIdx >= 0) { auto p = s.Strip(); auto idx = p.Int(); if (idx >= CurrentCommitIdx) { Unpulled = (int) (idx - CurrentCommitIdx); Update(); } } break; default: break; } return false; } void VcFolder::ListWorkingFolder() { if (IsListingWorking) return; d->ClearFiles(); bool Untracked = d->IsMenuChecked(IDM_UNTRACKED); LString Arg; switch (GetType()) { case VcPending: OnVcsTypeEvents.Add([this]() { ListWorkingFolder(); }); break; case VcCvs: if (Untracked) Arg = "-qn update"; else Arg = "-q diff --brief"; break; case VcSvn: Arg = "status"; break; case VcGit: #if 1 Arg = "-P status -vv"; #else Arg = "-P diff --diff-filter=CMRTU --cached"; #endif break; case VcHg: Arg = "status -mard"; break; default: return; } IsListingWorking = StartCmd(Arg, &VcFolder::ParseWorking); } void VcFolder::GitAdd() { if (!PostAdd) return; LString Args; if (PostAdd->Files.Length() == 0) { LString m(PostAdd->Msg); m = m.Replace("\"", "\\\""); Args.Printf("commit -m \"%s\"", m.Get()); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, PostAdd->Param, LogNormal); PostAdd.Reset(); } else { char NativeSep[] = {GetPathSep(), 0}; LString Last = PostAdd->Files.Last(); Args.Printf("add \"%s\"", Last.Replace("\"", "\\\"").Replace("/", NativeSep).Get()); PostAdd->Files.PopLast(); StartCmd(Args, &VcFolder::ParseGitAdd, NULL, LogNormal); } } bool VcFolder::ParseGitAdd(int Result, LString s, ParseParams *Params) { if (Result) { OnCmdError(s, "add failed."); } else { GitAdd(); } return false; } bool VcFolder::ParseCommit(int Result, LString s, ParseParams *Params) { if (LTreeItem::Select()) Select(true); CommitListDirty = Result == 0; CurrentCommit.Empty(); IsCommit = false; if (Result) { switch (GetType()) { case VcGit: { if (s.Find("Please tell me who you are") >= 0) { auto i = new LInput(GetTree(), "", "Git user name:", AppName); i->DoModal([this, i](auto dlg, auto ctrlId) { if (ctrlId) { LString Args; Args.Printf("config --global user.name \"%s\"", i->GetStr().Get()); StartCmd(Args); auto inp = new LInput(GetTree(), "", "Git user email:", AppName); i->DoModal([this, inp](auto dlg, auto ctrlId) { if (ctrlId) { LString Args; Args.Printf("config --global user.email \"%s\"", inp->GetStr().Get()); StartCmd(Args); } delete dlg; }); } delete dlg; }); } break; } default: break; } return false; } if (Result == 0 && LTreeItem::Select()) { d->ClearFiles(); auto *w = d->Diff ? d->Diff->GetWindow() : NULL; if (w) w->SetCtrlName(IDC_MSG, NULL); } switch (GetType()) { case VcGit: { Unpushed++; CommitListDirty = true; Update(); if (Params && Params->Str.Find("Push") >= 0) Push(); break; } case VcSvn: { CurrentCommit.Empty(); CommitListDirty = true; GetTree()->SendNotify((LNotifyType)LvcCommandEnd); if (!Result) { Unpushed = 0; Update(); GetCss(true)->Color(LColour::Green); } break; } case VcHg: { CurrentCommit.Empty(); CommitListDirty = true; GetTree()->SendNotify((LNotifyType)LvcCommandEnd); if (!Result) { Unpushed = 0; Update(); if (Params && Params->Str.Find("Push") >= 0) Push(); else GetCss(true)->Color(LColour::Green); } break; } case VcCvs: { CurrentCommit.Empty(); CommitListDirty = true; GetTree()->SendNotify((LNotifyType)LvcCommandEnd); if (!Result) { Unpushed = 0; Update(); GetCss(true)->Color(LColour::Green); } break; } default: { LAssert(!"Impl me."); break; } } return true; } void VcFolder::Commit(const char *Msg, const char *Branch, bool AndPush) { LArray Add; bool Partial = false; for (auto fp: *d->Files) { VcFile *f = dynamic_cast(fp); if (f) { int c = f->Checked(); if (c > 0) Add.Add(f); else Partial = true; } } if (CurrentBranch && Branch && !CurrentBranch.Equals(Branch)) { int Response = LgiMsg(GetTree(), "Do you want to start a new branch?", AppName, MB_YESNO); if (Response != IDYES) return; LJson j; j.Set("Command", "commit"); j.Set("Msg", Msg); j.Set("AndPush", (int64_t)AndPush); StartBranch(Branch, j.GetJson()); return; } if (!IsCommit) { LString Args; ParseParams *Param = AndPush ? new ParseParams("Push") : NULL; switch (GetType()) { case VcGit: { if (Add.Length() == 0) { break; } else if (Partial) { if (PostAdd.Reset(new GitCommit)) { PostAdd->Files.SetFixedLength(false); for (auto f : Add) PostAdd->Files.Add(f->GetFileName()); PostAdd->Msg = Msg; PostAdd->Branch = Branch; PostAdd->Param = Param; GitAdd(); } } else { LString m(Msg); m = m.Replace("\"", "\\\""); Args.Printf("commit -am \"%s\"", m.Get()); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal); } break; } case VcSvn: { LString::Array a; a.New().Printf("commit -m \"%s\"", Msg); for (auto pf: Add) { LString s = pf->GetFileName(); if (s.Find(" ") >= 0) a.New().Printf("\"%s\"", s.Get()); else a.New() = s; } Args = LString(" ").Join(a); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal); if (d->Tabs && IsCommit) { d->Tabs->Value(1); GetTree()->SendNotify((LNotifyType)LvcCommandStart); } break; } case VcHg: { LString::Array a; LString CommitMsg = Msg; TmpFile Tmp; if (CommitMsg.Find("\n") >= 0) { Tmp.Create().Write(Msg); a.New().Printf("commit -l \"%s\"", Tmp.GetName()); } else { a.New().Printf("commit -m \"%s\"", Msg); } if (Partial) { for (auto pf: Add) { LString s = pf->GetFileName(); if (s.Find(" ") >= 0) a.New().Printf("\"%s\"", s.Get()); else a.New() = s; } } Args = LString(" ").Join(a); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal); if (d->Tabs && IsCommit) { d->Tabs->Value(1); GetTree()->SendNotify((LNotifyType)LvcCommandStart); } break; } case VcCvs: { LString a; a.Printf("commit -m \"%s\"", Msg); IsCommit = StartCmd(a, &VcFolder::ParseCommit, NULL, LogNormal); break; } default: { OnCmdError(LString(), "No commit impl for type."); break; } } } } bool VcFolder::ParseStartBranch(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcHg: { if (Result == 0 && Params && Params->Str) { LJson j(Params->Str); auto cmd = j.Get("Command"); if (cmd.Equals("commit")) { auto Msg = j.Get("Msg"); auto AndPush = j.Get("AndPush").Int(); if (Msg) { Commit(Msg, NULL, AndPush > 0); } } } break; } default: { OnCmdError(LString(), "No commit impl for type."); break; } } return true; } void VcFolder::StartBranch(const char *BranchName, const char *OnCreated) { if (!BranchName) return; switch (GetType()) { case VcHg: { LString a; a.Printf("branch \"%s\"", BranchName); StartCmd(a, &VcFolder::ParseStartBranch, OnCreated ? new ParseParams(OnCreated) : NULL); break; } default: { NoImplementation(_FL); break; } } } void VcFolder::Push(bool NewBranchOk) { LString Args; bool Working = false; switch (GetType()) { case VcHg: { auto args = NewBranchOk ? "push --new-branch" : "push"; Working = StartCmd(args, &VcFolder::ParsePush, NULL, LogNormal); break; } case VcGit: { LString args; if (NewBranchOk) { if (CurrentBranch) { args.Printf("push --set-upstream origin %s", CurrentBranch.Get()); } else { OnCmdError(LString(), "Don't have the current branch?"); return; } } else { args = "push"; } Working = StartCmd(args, &VcFolder::ParsePush, NULL, LogNormal); break; } case VcSvn: { // Nothing to do here.. the commit pushed the data already break; } default: { OnCmdError(LString(), "No push impl for type."); break; } } if (d->Tabs && Working) { d->Tabs->Value(1); GetTree()->SendNotify((LNotifyType)LvcCommandStart); } } bool VcFolder::ParsePush(int Result, LString s, ParseParams *Params) { bool Status = false; if (Result) { bool needsNewBranchPerm = false; switch (GetType()) { case VcHg: { needsNewBranchPerm = s.Find("push creates new remote branches") >= 0; break; } case VcGit: { needsNewBranchPerm = s.Find("The current branch") >= 0 && s.Find("has no upstream branch") >= 0; break; } } if (needsNewBranchPerm && LgiMsg(GetTree(), LLoadString(IDS_CREATE_NEW_BRANCH), AppName, MB_YESNO) == IDYES) { Push(true); return false; } OnCmdError(s, "Push failed."); } else { switch (GetType()) { case VcGit: break; case VcSvn: break; default: break; } Unpushed = 0; GetCss(true)->Color(LColour::Green); Update(); Status = true; } GetTree()->SendNotify((LNotifyType)LvcCommandEnd); return Status; // no reselect } void VcFolder::Pull(int AndUpdate, LoggingType Logging) { bool Status = false; if (AndUpdate < 0) AndUpdate = GetTree()->GetWindow()->GetCtrlValue(IDC_UPDATE) != 0; switch (GetType()) { case VcNone: return; case VcHg: Status = StartCmd(AndUpdate ? "pull -u" : "pull", &VcFolder::ParsePull, NULL, Logging); break; case VcGit: Status = StartCmd(AndUpdate ? "pull" : "fetch", &VcFolder::ParsePull, NULL, Logging); break; case VcSvn: Status = StartCmd("up", &VcFolder::ParsePull, NULL, Logging); break; case VcPending: OnVcsTypeEvents.New() = [this, AndUpdate, Logging]() { Pull(AndUpdate, Logging); }; break; default: NoImplementation(_FL); break; } if (d->Tabs && Status) { d->Tabs->Value(1); GetTree()->SendNotify((LNotifyType)LvcCommandStart); } } bool VcFolder::ParsePull(int Result, LString s, ParseParams *Params) { GetTree()->SendNotify((LNotifyType)LvcCommandEnd); if (Result) { OnCmdError(s, "Pull failed."); return false; } else ClearError(); switch (GetType()) { case VcGit: { // Git does a merge by default, so the current commit changes... CurrentCommit.Empty(); break; } case VcHg: { CurrentCommit.Empty(); auto Lines = s.SplitDelimit("\n"); bool HasUpdates = false; for (auto Ln: Lines) { if (Ln.Find("files updated") < 0) continue; auto Parts = Ln.Split(","); for (auto p: Parts) { auto n = p.Strip().Split(" ", 1); if (n.Length() == 2) { if (n[0].Int() > 0) HasUpdates = true; } } } if (HasUpdates) GetCss(true)->Color(LColour::Green); else GetCss(true)->Color(LCss::ColorInherit); break; } case VcSvn: { // Svn also does a merge by default and can update our current position... CurrentCommit.Empty(); LString::Array a = s.SplitDelimit("\r\n"); for (auto &Ln: a) { if (Ln.Find("At revision") >= 0) { LString::Array p = Ln.SplitDelimit(" ."); CurrentCommit = p.Last(); break; } else if (Ln.Find("svn cleanup") >= 0) { OnCmdError(s, "Needs cleanup"); break; } } if (Params && Params->Str.Equals("log")) { LVariant Limit; d->Opts.GetValue("svn-limit", Limit); LString Args; if (Limit.CastInt32() > 0) Args.Printf("log --limit %i", Limit.CastInt32()); else Args = "log"; IsLogging = StartCmd(Args, &VcFolder::ParseLog); return false; } break; } default: break; } CommitListDirty = true; return true; // Yes - reselect and update } void VcFolder::MergeToLocal(LString Rev) { switch (GetType()) { case VcGit: { LString Args; Args.Printf("merge -m \"Merge with %s\" %s", Rev.Get(), Rev.Get()); StartCmd(Args, &VcFolder::ParseMerge, NULL, LogNormal); break; } case VcHg: { LString Args; Args.Printf("merge -r %s", Rev.Get()); StartCmd(Args, &VcFolder::ParseMerge, NULL, LogNormal); break; } default: LgiMsg(GetTree(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE), AppName); break; } } bool VcFolder::ParseMerge(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcGit: case VcHg: if (Result == 0) CommitListDirty = true; else OnCmdError(s, LLoadString(IDS_ERR_MERGE_FAILED)); break; default: LAssert(!"Impl me."); break; } return true; } void VcFolder::Refresh() { CommitListDirty = true; CurrentCommit.Empty(); GitNames.Empty(); Branches.DeleteObjects(); if (Uncommit && Uncommit->LListItem::Select()) Uncommit->Select(true); Select(true); } void VcFolder::Clean() { switch (GetType()) { case VcSvn: StartCmd("cleanup", &VcFolder::ParseClean, NULL, LogNormal); break; default: LgiMsg(GetTree(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE), AppName); break; } } bool VcFolder::ParseClean(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcSvn: if (Result == 0) GetCss(true)->Color(LCss::ColorInherit); break; default: LAssert(!"Impl me."); break; } return false; } LColour VcFolder::BranchColour(const char *Name) { if (!Name) return GetPaletteColour(0); auto b = Branches.Find(Name); if (!b) // Must be a new one? { int i = 1; for (auto b: Branches) { auto &v = b.value; if (!v->Colour.IsValid()) { if (v->Default) v->Colour = GetPaletteColour(0); else v->Colour = GetPaletteColour(i++); } } Branches.Add(Name, b = new VcBranch(Name)); b->Colour = GetPaletteColour((int)Branches.Length()); } return b ? b->Colour : GetPaletteColour(0); } void VcFolder::CurrentRev(std::function Callback) { LString Cmd; Cmd.Printf("id -i"); RunCmd(Cmd, LogNormal, [Callback](auto r) { if (r.Code == 0) Callback(r.Out.Strip()); }); } bool VcFolder::RenameBranch(LString NewName, LArray &Revs) { switch (GetType()) { case VcHg: { // Update to the ancestor of the commits LHashTbl,int> Refs(0, -1); for (auto c: Revs) { for (auto p:*c->GetParents()) if (Refs.Find(p) < 0) Refs.Add(p, 0); if (Refs.Find(c->GetRev()) >= 0) Refs.Add(c->GetRev(), 1); } LString::Array Ans; for (auto i:Refs) { if (i.value == 0) Ans.Add(i.key); } LArray Ancestors = d->GetRevs(Ans); if (Ans.Length() != 1) { // We should only have one ancestor LString s, m; s.Printf("Wrong number of ancestors: " LPrintfInt64 ".\n", Ans.Length()); for (auto i: Ancestors) { m.Printf("\t%s\n", i->GetRev()); s += m; } LgiMsg(GetTree(), s, AppName, MB_OK); break; } LArray Top; for (auto c:Revs) { for (auto p:*c->GetParents()) if (Refs.Find(p) == 0) Top.Add(c); } if (Top.Length() != 1) { d->Log->Print("Error: Can't find top most commit. (%s:%i)\n", _FL); return false; } // Create the new branch... auto First = Ancestors.First(); LString Cmd; Cmd.Printf("update -r " LPrintfInt64, First->GetIndex()); RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, &Top](auto r) { if (r.Code) { d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL); return; } Cmd.Printf("branch \"%s\"", NewName.Get()); RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, &Top](auto r) { if (r.Code) { d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL); return; } // Commit it to get a revision point to rebase to Cmd.Printf("commit -m \"Branch: %s\"", NewName.Get()); RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, &Top](auto r) { if (r.Code) { d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL); return; } CurrentRev([this, &Cmd, NewName, &Top](auto BranchNode) { // Rebase the old tree to this point Cmd.Printf("rebase -s %s -d %s", Top.First()->GetRev(), BranchNode.Get()); RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, Top](auto r) { if (r.Code) { d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL); return; } CommitListDirty = true; d->Log->Print("Finished rename.\n", _FL); }); }); }); }); }); break; } default: { LgiMsg(GetTree(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE), AppName); break; } } return true; } bool VcFolder::ParseAddFile(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcCvs: { if (Result) { d->Tabs->Value(1); OnCmdError(s, LLoadString(IDS_ERR_ADD_FAILED)); } else ClearError(); break; } default: break; } return false; } bool VcFolder::AddFile(const char *Path, bool AsBinary) { if (!Path) return false; switch (GetType()) { case VcCvs: { auto p = LString(Path).RSplit(DIR_STR, 1); ParseParams *params = NULL; if (p.Length() >= 2) { if ((params = new ParseParams)) params->AltInitPath = p[0]; } LString a; a.Printf("add%s \"%s\"", AsBinary ? " -kb" : "", p.Length() > 1 ? p.Last().Get() : Path); return StartCmd(a, &VcFolder::ParseAddFile, params); break; } default: { NoImplementation(_FL); break; } } return false; } bool VcFolder::ParseRevert(int Result, LString s, ParseParams *Params) { if (GetType() == VcSvn) { if (s.Find("Skipped ") >= 0) Result = 1; // Stupid svn... *sigh* } if (Result) { OnCmdError(s, LLoadString(IDS_ERR_REVERT_FAILED)); } ListWorkingFolder(); return false; } bool VcFolder::Revert(LString::Array &Uris, const char *Revision) { if (Uris.Length() == 0) return false; switch (GetType()) { case VcGit: { LStringPipe cmd, paths; LAutoPtr params; if (Revision) { cmd.Print("checkout %s", Revision); } else { // Unstage the file... cmd.Print("reset"); } for (auto u: Uris) { auto Path = GetFilePart(u); paths.Print(" \"%s\"", Path.Get()); } auto p = paths.NewLStr(); cmd.Write(p); if (!Revision) { if (params.Reset(new ParseParams)) { params->Callback = [this, p](auto code, auto str) { LString c; c.Printf("checkout %s", p.Get()); StartCmd(c, &VcFolder::ParseRevert); }; } } return StartCmd(cmd.NewLStr(), &VcFolder::ParseRevert, params.Release()); break; } case VcHg: case VcSvn: { LStringPipe p; if (Revision) p.Print("up -r %s", Revision); else p.Print("revert"); for (auto u: Uris) { auto Path = GetFilePart(u); p.Print(" \"%s\"", Path.Get()); } auto a = p.NewLStr(); return StartCmd(a, &VcFolder::ParseRevert); break; } default: { NoImplementation(_FL); break; } } return false; } bool VcFolder::ParseResolveList(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcHg: { auto lines = s.Replace("\r").Split("\n"); for (auto &ln: lines) { auto p = ln.Split(" ", 1); if (p.Length() == 2) { if (p[0].Equals("U")) { auto f = new VcFile(d, this, LString(), true); f->SetText(p[0], COL_STATE); f->SetText(p[1], COL_FILENAME); f->GetStatus(); d->Files->Insert(f); } } } break; } default: { NoImplementation(_FL); break; } } return true; } bool VcFolder::ParseResolve(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcGit: { break; } case VcHg: { d->Log->Print("Resolve: %s\n", s.Get()); break; } default: { NoImplementation(_FL); break; } } return true; } bool VcFolder::Resolve(const char *Path, LvcResolve Type) { if (!Path) return false; switch (GetType()) { case VcGit: { LString a; auto local = GetFilePart(Path); LAutoPtr params(new ParseParams(Path)); switch (Type) { case ResolveIncoming: a.Printf("checkout --theirs \"%s\"", local.Get()); break; case ResolveLocal: a.Printf("checkout --ours \"%s\"", local.Get()); break; case ResolveMark: a.Printf("add \"%s\"", local.Get()); break; default: OnCmdError(Path, "No resolve type implemented."); return false; } if (Type == ResolveIncoming || Type == ResolveLocal) { // Add the file after the resolution: params->Callback = [this, local](auto code, auto str) { LString a; a.Printf("add \"%s\"", local.Get()); StartCmd(a, &VcFolder::ParseAddFile); Refresh(); }; } return StartCmd(a, &VcFolder::ParseResolve, params.Release()); } case VcHg: { LString a; auto local = GetFilePart(Path); switch (Type) { case ResolveMark: a.Printf("resolve -m \"%s\"", local.Get()); break; case ResolveUnmark: a.Printf("resolve -u \"%s\"", local.Get()); break; case ResolveLocal: a.Printf("resolve -t internal:local \"%s\"", local.Get()); break; case ResolveIncoming: a.Printf("resolve -t internal:other \"%s\"", local.Get()); break; default: break; } if (a) return StartCmd(a, &VcFolder::ParseResolve, new ParseParams(Path)); break; } case VcSvn: case VcCvs: default: { NoImplementation(_FL); break; } } return false; } bool BlameLine::Parse(VersionCtrl type, LArray &out, LString in) { auto lines = in.SplitDelimit("\n", -1, false); switch (type) { case VcGit: { for (auto &ln: lines) { auto s = ln.Get(); auto open = ln.Find("("); auto close = ln.Find(")", open); if (open > 0 && close > open) { auto eRef = ln(0, open-1); auto fields = ln(open + 1, close); auto parts = fields.SplitDelimit(); auto &o = out.New(); o.ref = eRef; o.line = parts.Last(); parts.PopLast(); LString::Array name; LDateTime dt; for (auto p: parts) { auto first = p(0); if (IsDigit(first)) { if (p.Find("-") > 0) dt.SetDate(p); else if (p.Find(":") > 0) dt.SetTime(p); } else if (first == '+') dt.SetTimeZone((int)p.Int(), false); else name.Add(p); } o.user = LString(" ").Join(name); o.date = dt.Get(); o.src = ln(close + 1, -1); } else if (ln.Length() > 0) { int asd=0; } } break; } case VcHg: { for (auto &ln: lines) { auto s = ln.Get(); auto eUser = strchr(s, ' '); if (!eUser) continue; auto eRef = strchr(eUser, ':'); if (!eRef) continue; auto &o = out.New(); o.user.Set(s, eUser++ - s); o.ref.Set(eUser, eRef - eUser); o.src = eRef + 1; } break; } /* case VcSvn: { break; } */ default: { LAssert(0); return false; } } return true; } bool VcFolder::ParseBlame(int Result, LString s, ParseParams *Params) { if (!Params) { LAssert(!"Need the path in the params."); return false; } LArray lines; if (BlameLine::Parse(GetType(), lines, s)) { if (auto ui = new BrowseUi(BrowseUi::TBlame, d, this, Params->Str)) ui->ParseBlame(lines, s); } else NoImplementation(_FL); return false; } bool VcFolder::Blame(const char *Path) { if (!Path) return false; auto file = GetFilePart(Path); LAutoPtr Params(new ParseParams(file)); LUri u(Path); switch (GetType()) { case VcGit: { LString a; a.Printf("-P blame \"%s\"", file.Get()); return StartCmd(a, &VcFolder::ParseBlame, Params.Release()); break; } case VcHg: { LString a; a.Printf("annotate -un \"%s\"", file.Get()); return StartCmd(a, &VcFolder::ParseBlame, Params.Release()); break; } case VcSvn: { LString a; a.Printf("blame \"%s\"", file.Get()); return StartCmd(a, &VcFolder::ParseBlame, Params.Release()); break; } default: { NoImplementation(_FL); break; } } return true; } bool VcFolder::SaveFileAs(const char *Path, const char *Revision) { if (!Path || !Revision) return false; return true; } bool VcFolder::ParseSaveAs(int Result, LString s, ParseParams *Params) { return false; } bool VcFolder::ParseCounts(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcGit: { Unpushed = (int) s.Strip().Split("\n").Length(); break; } case VcSvn: { int64 ServerRev = 0; bool HasUpdate = false; LString::Array c = s.Split("\n"); for (unsigned i=0; i 1 && a[0].Equals("Status")) ServerRev = a.Last().Int(); else if (a[0].Equals("*")) HasUpdate = true; } if (ServerRev > 0 && HasUpdate) { int64 CurRev = CurrentCommit.Int(); Unpulled = (int) (ServerRev - CurRev); } else Unpulled = 0; Update(); break; } default: { LAssert(!"Impl me."); break; } } IsUpdatingCounts = false; Update(); return false; // No re-select } void VcFolder::SetEol(const char *Path, int Type) { if (!Path) return; switch (Type) { case IDM_EOL_LF: { ConvertEol(Path, false); break; } case IDM_EOL_CRLF: { ConvertEol(Path, true); break; } case IDM_EOL_AUTO: { #ifdef WINDOWS ConvertEol(Path, true); #else ConvertEol(Path, false); #endif break; } } } void VcFolder::UncommitedItem::Select(bool b) { LListItem::Select(b); if (b) { LTreeItem *i = d->Tree->Selection(); VcFolder *f = dynamic_cast(i); if (f) f->ListWorkingFolder(); if (d->Msg) { d->Msg->Name(NULL); auto *w = d->Msg->GetWindow(); if (w) { w->SetCtrlEnabled(IDC_COMMIT, true); w->SetCtrlEnabled(IDC_COMMIT_AND_PUSH, true); } } } } void VcFolder::UncommitedItem::OnPaint(LItem::ItemPaintCtx &Ctx) { LFont *f = GetList()->GetFont(); f->Transparent(false); f->Colour(Ctx.Fore, Ctx.Back); LDisplayString ds(f, "(working folder)"); ds.Draw(Ctx.pDC, Ctx.x1 + ((Ctx.X() - ds.X()) / 2), Ctx.y1 + ((Ctx.Y() - ds.Y()) / 2), &Ctx); } ////////////////////////////////////////////////////////////////////////////////////////// VcLeaf::VcLeaf(VcFolder *parent, LTreeItem *Item, LString uri, LString leaf, bool folder) { Parent = parent; d = Parent->GetPriv(); LAssert(uri.Find("://") >= 0); // Is URI Uri.Set(uri); LAssert(Uri); Leaf = leaf; Folder = folder; Item->Insert(this); if (Folder) { Insert(Tmp = new LTreeItem); Tmp->SetText("Loading..."); } } VcLeaf::~VcLeaf() { for (auto l: Log) { if (!l->GetList()) delete l; } } LString VcLeaf::Full() { LUri u = Uri; u += Leaf; return u.ToString(); } void VcLeaf::OnBrowse() { auto full = Full(); LList *Files = d->Files; Files->Empty(); LDirectory Dir; for (int b = Dir.First(full); b; b = Dir.Next()) { if (Dir.IsDir()) continue; VcFile *f = new VcFile(d, Parent, LString(), true); if (f) { f->SetUri(LString("file://") + full); f->SetText(Dir.GetName(), COL_FILENAME); Files->Insert(f); } } Files->ResizeColumnsToContent(); if (Folder) Parent->FolderStatus(full, this); } void VcLeaf::AfterBrowse() { } VcLeaf *VcLeaf::FindLeaf(const char *Path, bool OpenTree) { if (!Stricmp(Path, Full().Get())) return this; if (OpenTree) DoExpand(); VcLeaf *r = NULL; for (auto n = GetChild(); !r && n; n = n->GetNext()) { auto l = dynamic_cast(n); if (l) r = l->FindLeaf(Path, OpenTree); } return r; } void VcLeaf::DoExpand() { if (Tmp) { Tmp->Remove(); DeleteObj(Tmp); Parent->ReadDir(this, Full()); } } void VcLeaf::OnExpand(bool b) { if (b) DoExpand(); } const char *VcLeaf::GetText(int Col) { if (Col == 0) return Leaf; return NULL; } int VcLeaf::GetImage(int Flags) { return Folder ? IcoFolder : IcoFile; } int VcLeaf::Compare(VcLeaf *b) { // Sort folders to the top... if (Folder ^ b->Folder) return (int)b->Folder - (int)Folder; // Then alphabetical return Stricmp(Leaf.Get(), b->Leaf.Get()); } bool VcLeaf::Select() { return LTreeItem::Select(); } void VcLeaf::Select(bool b) { LTreeItem::Select(b); if (b) { d->Commits->RemoveAll(); OnBrowse(); ShowLog(); } } void VcLeaf::ShowLog() { if (!Log.Length()) return; d->Commits->RemoveAll(); Parent->DefaultFields(); Parent->UpdateColumns(); for (auto i: Log) // We make a copy of the commit here so that the LList owns the copied object, // and this object still owns 'i'. d->Commits->Insert(new VcCommit(*i), -1, false); d->Commits->UpdateAllItems(); d->Commits->ResizeColumnsToContent(); } void VcLeaf::OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { LSubMenu s; s.AppendItem("Log", IDM_LOG); s.AppendItem("Blame", IDM_BLAME, !Folder); s.AppendSeparator(); s.AppendItem("Browse To", IDM_BROWSE_FOLDER); s.AppendItem("Terminal At", IDM_TERMINAL); int Cmd = s.Float(GetTree(), m - _ScrollPos()); switch (Cmd) { case IDM_LOG: { Parent->LogFile(Full()); break; } case IDM_BLAME: { Parent->Blame(Full()); break; } case IDM_BROWSE_FOLDER: { LBrowseToFile(Full()); break; } case IDM_TERMINAL: { TerminalAt(Full()); break; } } } } ///////////////////////////////////////////////////////////////////////////////////////// ProcessCallback::ProcessCallback(LString exe, LString args, LString localPath, LTextLog *log, LView *view, std::function callback) : Log(log), View(view), Callback(callback), LThread("ProcessCallback.Thread"), LSubProcess(exe, args) { SetInitFolder(localPath); if (Log) Log->Print("%s %s\n", exe.Get(), args.Get()); Run(); } int ProcessCallback::Main() { if (!Start()) { Ret.Out.Printf("Process failed with %i", GetErrorCode()); Callback(Ret); } else { while (IsRunning()) { auto Rd = Read(); if (Rd.Length()) { Ret.Out += Rd; if (Log) Log->Write(Rd.Get(), Rd.Length()); } } auto Rd = Read(); if (Rd.Length()) { Ret.Out += Rd; if (Log) Log->Write(Rd.Get(), Rd.Length()); } Ret.Code = GetExitValue(); } View->PostEvent(M_HANDLE_CALLBACK, (LMessage::Param)this); return 0; } void ProcessCallback::OnComplete() // Called in the GUI thread... { Callback(Ret); } diff --git a/lvc/src/VcFolder.h b/lvc/src/VcFolder.h --- a/lvc/src/VcFolder.h +++ b/lvc/src/VcFolder.h @@ -1,415 +1,415 @@ #ifndef _VcFolder_h_ #define _VcFolder_h_ #include #include "lgi/common/SubProcess.h" #include "lgi/common/Uri.h" #include "SshConnection.h" class VcLeaf; enum LoggingType { LogNone, // No output from cmd LogNormal, // Output appears as it's available LogSilo, // Output appears after cmd finished (keeps it non-interleaved with other log msgs) LogDebug, // Debug log level }; enum LvcError { ErrNone, - ErrSubProcessFailed = GSUBPROCESS_ERROR, + ErrSubProcessFailed = LSUBPROCESS_ERROR, }; enum LvcStatus { StatusNone, StatusActive, StatusError, }; enum LvcResolve { ResolveNone, //--- ResolveMark, ResolveUnmark, //--- ResolveLocal, ResolveIncoming, ResolveTool, }; class ReaderThread : public LThread { VersionCtrl Vcs; LStream *Out; LAutoPtr Process; int FilterCount; int OnLine(char *s, ssize_t len); bool OnData(char *Buf, ssize_t &r); public: int Result; ReaderThread(VersionCtrl vcs, LAutoPtr p, LStream *out); ~ReaderThread(); int Main(); }; extern int Ver2Int(LString v); extern int ToolVersion[VcMax]; extern int LstCmp(LListItem *a, LListItem *b, int Col); const char *GetVcName(VersionCtrl type); struct Result { int Code; LString Out; }; struct ProcessCallback : public LThread, public LSubProcess { Result Ret; LView *View = NULL; LTextLog *Log = NULL; std::function Callback; public: ProcessCallback(LString exe, LString args, LString localPath, LTextLog *log, LView *view, std::function callback); int Main(); void OnComplete(); }; struct VcBranch : public LString { bool Default; LColour Colour; LString Hash; VcBranch(LString name, LString hash = LString()) { Default = name.Equals("default") || name.Equals("trunk") || name.Equals("main"); Set(name); if (hash) Hash = hash; } }; #if HAS_LIBSSH struct SshParams { SshConnection *c = NULL; SshConnection::LoggingType LogType = SshConnection::LogNone; VcFolder *f = NULL; LString Exe, Args, Path, Output; VersionCtrl Vcs = VcNone; ParseFn Parser = NULL; ParseParams *Params = NULL; int ExitCode = -1; SshParams(SshConnection *con) : c(con) { } }; #endif class VcFolder : public LTreeItem { friend class VcCommit; struct Author { bool InProgress = false; LString name, email; operator bool() const { return !name.IsEmpty() && !email.IsEmpty(); } LString ToString() { return LString::Fmt("%s <%s>", name.Get(), email.Get()); } }; class Cmd : public LStream { LString::Array Context; LStringPipe Buf; public: LoggingType Logging; LStream *Log; LAutoPtr Rd; ParseFn PostOp; LAutoPtr Params; LvcError Err; Cmd(LString::Array &context, LoggingType logging, LStream *log) { Context = context; Logging = logging; Log = log; Err = ErrNone; } ~Cmd() { } LString GetBuf() { LString s = Buf.NewLStr(); if (Log && Logging == LogSilo) { LString m; m.Printf("=== %s ===\n\t%s %s\n", Context[0].Get(), Context[1].Get(), Context[2].Get()); Log->Write(m.Get(), m.Length()); auto Lines = s.Split("\n"); for (auto Ln : Lines) Log->Print("\t%s\n", Ln.Get()); } return s; } ssize_t Write(const void *Ptr, ssize_t Size, int Flags = 0) { ssize_t Wr = Buf.Write(Ptr, Size, Flags); if (Log && Logging == LogNormal) Log->Write(Ptr, Size, Flags); if (Flags) Err = (LvcError) Flags; return Wr; } }; class UncommitedItem : public LListItem { AppPriv *d; public: UncommitedItem(AppPriv *priv) { d = priv; } void OnPaint(LItem::ItemPaintCtx &Ctx); void Select(bool b); }; AppPriv *d; VersionCtrl Type = VcNone; LUri Uri; LString CurrentCommit, RepoUrl, VcCmd; int64 CurrentCommitIdx = -1; LArray Log; LString CurrentBranch; LHashTbl,VcBranch*> Branches; LAutoPtr Uncommit; LString Cache, NewRev; bool CommitListDirty = false; int Unpushed = -1, Unpulled = -1; LString CountCache; LTreeItem *Tmp = NULL; int CmdErrors = 0; LArray Fields; LArray> OnVcsTypeEvents; // Author name/email Author AuthorLocal, AuthorGlobal; bool IsGettingAuthor = false; // This is set when a blame or log is looking at a particular file, // and wants it selected after the file list is populated LString FileToSelect; void InsertFiles(List &files); // Git specific LHashTbl,LString> GitNames; void AddGitName(LString Hash, LString Name); LString GetGitNames(LString Hash); static int CmdMaxThreads; static int CmdActiveThreads; struct GitCommit { LString::Array Files; LString Msg, Branch; ParseParams *Param; GitCommit() { Param = NULL; } }; LAutoPtr PostAdd; void GitAdd(); LArray Cmds; bool IsLogging = false, IsUpdate = false, IsFilesCmd = false; bool IsCommit = false, IsUpdatingCounts = false; bool IsListingWorking = false; LvcStatus IsBranches = StatusNone, IsIdent = StatusNone; void Init(AppPriv *priv); const char *GetVcName(); char GetPathSep(); bool StartCmd(const char *Args, ParseFn Parser = NULL, ParseParams *Params = NULL, LoggingType Logging = LogNone); bool RunCmd(const char *Args, LoggingType Logging, std::function Callback); void OnBranchesChange(); void NoImplementation(const char *file, int line); void OnCmdError(LString Output, const char *Msg); void ClearError(); VcFile *FindFile(const char *Path); void LinkParents(); void CurrentRev(std::function Callback); LColour BranchColour(const char *Name); void UpdateBranchUi(); bool ParseDiffs(LString s, LString Rev, bool IsWorking); bool ParseRevList(int Result, LString s, ParseParams *Params); bool ParseLog(int Result, LString s, ParseParams *Params); bool ParseInfo(int Result, LString s, ParseParams *Params); bool ParseFiles(int Result, LString s, ParseParams *Params); bool ParseWorking(int Result, LString s, ParseParams *Params); bool ParseCheckout(int Result, LString s, ParseParams *Params); bool ParseCommit(int Result, LString s, ParseParams *Params); bool ParseGitAdd(int Result, LString s, ParseParams *Params); bool ParsePush(int Result, LString s, ParseParams *Params); bool ParsePull(int Result, LString s, ParseParams *Params); bool ParseCounts(int Result, LString s, ParseParams *Params); bool ParseRevert(int Result, LString s, ParseParams *Params); bool ParseResolveList(int Result, LString s, ParseParams *Params); bool ParseResolve(int Result, LString s, ParseParams *Params); bool ParseBlame(int Result, LString s, ParseParams *Params); bool ParseSaveAs(int Result, LString s, ParseParams *Params); bool ParseBranches(int Result, LString s, ParseParams *Params); bool ParseStatus(int Result, LString s, ParseParams *Params); bool ParseAddFile(int Result, LString s, ParseParams *Params); bool ParseVersion(int Result, LString s, ParseParams *Params); bool ParseClean(int Result, LString s, ParseParams *Params); bool ParseDiff(int Result, LString s, ParseParams *Params); bool ParseMerge(int Result, LString s, ParseParams *Params); bool ParseCountToTip(int Result, LString s, ParseParams *Params); bool ParseUpdateSubs(int Result, LString s, ParseParams *Params); bool ParseRemoteFind(int Result, LString s, ParseParams *Params); bool ParseStartBranch(int Result, LString s, ParseParams *Params); bool ParseSelectCommit(int Result, LString s, ParseParams *Params); void DoExpand(); public: VcFolder(AppPriv *priv, const char *uri); VcFolder(AppPriv *priv, LXmlTag *t); ~VcFolder(); VersionCtrl GetType(); AppPriv *GetPriv() { return d; } bool IsLocal(); const char *LocalPath(); LUri GetUri() { return Uri; } VcLeaf *FindLeaf(const char *Path, bool OpenTree); void DefaultFields(); void UpdateColumns(LList *lst = NULL); int IndexOfCommitField(CommitField fld); const char *GetText(int Col); LArray &GetFields() { return Fields; } bool Serialize(LXmlTag *t, bool Write); LString &GetCurrentBranch(); void SetCurrentBranch(LString name); LXmlTag *Save(); LString GetConfigFile(bool local); bool GetAuthor(bool local, std::function callback); bool SetAuthor(bool local, LString name, LString email); void UpdateAuthorUi(); void Empty(); void Select(bool b); void ListCommit(VcCommit *c); void ListWorkingFolder(); void FolderStatus(const char *Path = NULL, VcLeaf *Notify = NULL); void Commit(const char *Msg, const char *Branch, bool AndPush); void StartBranch(const char *BranchName, const char *OnCreated = NULL); void Push(bool NewBranchOk = false); void Pull(int AndUpdate = -1, LoggingType Logging = LogNormal); void Clean(); bool Revert(LString::Array &uris, const char *Revision = NULL); bool Resolve(const char *Path, LvcResolve Type); bool AddFile(const char *Path, bool AsBinary = true); bool Blame(const char *Path); bool SaveFileAs(const char *Path, const char *Revision); void ReadDir(LTreeItem *Parent, const char *Uri); void SetEol(const char *Path, int Type); void GetVersion(); void Diff(VcFile *file); void DiffRange(const char *FromRev, const char *ToRev); void MergeToLocal(LString Rev); bool RenameBranch(LString NewName, LArray &Revs); void Refresh(); bool GetBranches(ParseParams *Params = NULL); void GetCurrentRevision(ParseParams *Params = NULL); void CountToTip(); bool UpdateSubs(); // Clone/checkout any sub-repositories. void LogFilter(const char *Filter); void LogFile(const char *Path); void ClearLog(); LString GetFilePart(const char *uri); void FilterCurrentFiles(); void GetRemoteUrl(std::function Callback); void SelectCommit(LWindow *Parent, LString Commit, LString Path); void Checkout(const char *Rev, bool isBranch); void OnPulse(); void OnMouseClick(LMouse &m); void OnRemove(); void OnExpand(bool b); void OnVcsType(LString errorMsg); #if HAS_LIBSSH void OnSshCmd(SshParams *p); #endif }; class VcLeaf : public LTreeItem { AppPriv *d = NULL; VcFolder *Parent = NULL; bool Folder = false; LUri Uri; LString Leaf; LTreeItem *Tmp = NULL; void DoExpand(); public: LArray Log; VcLeaf(VcFolder *parent, LTreeItem *Item, LString uri, LString leaf, bool folder); ~VcLeaf(); LString Full(); VcLeaf *FindLeaf(const char *Path, bool OpenTree); void OnBrowse(); void AfterBrowse(); void OnExpand(bool b); const char *GetText(int Col); int GetImage(int Flags); int Compare(VcLeaf *b); bool Select(); void Select(bool b); void OnMouseClick(LMouse &m); void ShowLog(); }; #endif diff --git a/src/common/Gdc2/Filters/Png.cpp b/src/common/Gdc2/Filters/Png.cpp --- a/src/common/Gdc2/Filters/Png.cpp +++ b/src/common/Gdc2/Filters/Png.cpp @@ -1,1568 +1,1568 @@ /*hdr * FILE: Png.cpp * AUTHOR: Matthew Allen * DATE: 8/9/1998 * DESCRIPTION: Png file filter * * Copyright (C) 2002, Matthew Allen * fret@memecode.com */ // // 'png.h' comes from libpng, which you can get from: // http://www.libpng.org/pub/png/libpng.html // // You will also need zlib, which pnglib requires. zlib // is available here: // http://www.gzip.org/zlib // // If you don't want to build with PNG support then set // the define HAS_LIBPNG_ZLIB to '0' in Lgi.h // #ifndef __CYGWIN__ #include "math.h" #include "png.h" #endif #include "lgi/common/Lgi.h" #include "lgi/common/Palette.h" #ifdef __CYGWIN__ #include "png.h" #endif #include #include #include #include "lgi/common/LibraryUtils.h" #ifdef FILTER_UI #include "TransparentDlg.h" #endif #include "lgi/common/Variant.h" // Pixel formats typedef uint8_t Png8; typedef LRgb24 Png24; typedef LRgba32 Png32; typedef LRgb48 Png48; typedef LRgba64 Png64; #if PNG_LIBPNG_VER_MAJOR <= 1 && PNG_LIBPNG_VER_MINOR <= 2 #define png_const_infop png_infop #define png_const_bytep png_bytep #endif #ifdef LINUX const char *LinuxLibName() { static char lib[64]; sprintf_s(lib, sizeof(lib), "libpng%i", PNG_LIBPNG_VER_SONUM); // printf("png lib name = '%s'\n", lib); return lib; } #endif #if LIBPNG_SHARED #define LIBPNG Lib-> const char *sLibrary = #if defined(MAC) "libpng16" #elif defined(HAIKU) "libpng16.so.16" #elif defined(LINUX) LinuxLibName() #else #if defined(__CYGWIN__) "cygpng12" #else "libpng16" #ifdef _MSC_VER_STR "_" #if _MSC_VER >= _MSC_VER_VS2019 - _MSC_YEAR_STR + _MSC_YEAR_STR #else - _MSC_VER_STR + _MSC_VER_STR #endif #if defined(LGI_64BIT) - "x64" + "x64" #else - "x32" + "x32" #endif #ifdef _DEBUG - "d" + "d" #endif #endif #endif #endif ; // Library interface class LibPng : public LLibrary { public: LibPng() : LLibrary(sLibrary) { static bool First = true; if (First) { First = false; auto Loaded = IsLoaded(); if (Loaded) { LgiTrace("%s:%i - PNG: %s\n", _FL, GetFullPath().Get()); } else { #if defined(WINDOWS) && defined(_DEBUG) auto ReleaseLib = LString(sLibrary)(0, -2); if (!Load(ReleaseLib)) #endif LgiTrace("%s:%i - Failed to load '%s'.\n", _FL, sLibrary); } } } DynFunc4( png_structp, png_create_read_struct, png_const_charp, user_png_ver, png_voidp, error_ptr, png_error_ptr, error_fn, png_error_ptr, warn_fn); DynFunc4( png_structp, png_create_write_struct, png_const_charp, user_png_ver, png_voidp, error_ptr, png_error_ptr, error_fn, png_error_ptr, warn_fn); DynFunc1( png_infop, png_create_info_struct, png_structp, png_ptr); DynFunc2( int, png_destroy_info_struct, png_structp, png_ptr, png_infopp, info_ptr); DynFunc3( int, png_destroy_read_struct, png_structpp, png_ptr_ptr, png_infopp, info_ptr_ptr, png_infopp, end_info_ptr_ptr); DynFunc2( int, png_destroy_write_struct, png_structpp, png_ptr_ptr, png_infopp, info_ptr_ptr); DynFunc3( int, png_set_read_fn, png_structp, png_ptr, png_voidp, io_ptr, png_rw_ptr, read_data_fn); DynFunc4( int, png_set_write_fn, png_structp, png_ptr, png_voidp, io_ptr, png_rw_ptr, write_data_fn, png_flush_ptr, output_flush_fn); DynFunc4( int, png_read_png, png_structp, png_ptr, png_infop, info_ptr, int, transforms, png_voidp, params); DynFunc4( int, png_write_png, png_structp, png_ptr, png_infop, info_ptr, int, transforms, png_voidp, params); DynFunc2( png_bytepp, png_get_rows, png_structp, png_ptr, png_infop, info_ptr); DynFunc3( int, png_set_rows, png_structp, png_ptr, png_infop, info_ptr, png_bytepp, row_pointers); DynFunc6( png_uint_32, png_get_iCCP, png_structp, png_ptr, png_const_infop, info_ptr, png_charpp, name, int*, compression_type, png_bytepp, profile, png_uint_32*, proflen); DynFunc6( int, png_set_iCCP, png_structp, png_ptr, png_infop, info_ptr, png_charp, name, int, compression_type, png_const_bytep, profile, png_uint_32, proflen); DynFunc5( png_uint_32, png_get_tRNS, png_structp, png_ptr, png_infop, info_ptr, png_bytep*, trans_alpha, int*, num_trans, png_color_16p*, trans_color); DynFunc3( png_uint_32, png_get_valid, png_structp, png_ptr, png_infop, info_ptr, png_uint_32, flag); DynFunc4( png_uint_32, png_get_PLTE, png_structp, png_ptr, png_infop, info_ptr, png_colorp*, palette, int*, num_palette); DynFunc2( png_uint_32, png_get_image_width, png_structp, png_ptr, png_infop, info_ptr); DynFunc2( png_uint_32, png_get_image_height, png_structp, png_ptr, png_infop, info_ptr); DynFunc2( png_byte, png_get_channels, png_structp, png_ptr, png_infop, info_ptr); #if 1 // PNG_LIBPNG_VER <= 10250 DynFunc2( png_byte, png_get_color_type, png_structp, png_ptr, png_infop, info_ptr); #else DynFunc2( png_byte, png_get_color_type, png_const_structp, png_ptr, png_const_infop, info_ptr); #endif DynFunc2( png_byte, png_get_bit_depth, png_structp, png_ptr, png_infop, info_ptr); DynFunc1( png_voidp, png_get_error_ptr, png_structp, png_ptr); DynFunc1( png_voidp, png_get_io_ptr, png_structp, png_ptr); DynFunc9( int, png_set_IHDR, png_structp, png_ptr, png_infop, info_ptr, png_uint_32, width, png_uint_32, height, int, bit_depth, int, color_type, int, interlace_method, int, compression_method, int, filter_method); DynFunc4( int, png_set_PLTE, png_structp, png_ptr, png_infop, info_ptr, png_colorp, palette, int, num_palette); DynFunc5( int, png_set_tRNS, png_structp, png_ptr, png_infop, info_ptr, png_bytep, trans_alpha, int, num_trans, png_color_16p, trans_color); /* DynFunc2( png_byte, png_get_interlace_type, png_const_structp, png_ptr, png_const_infop, info_ptr); */ }; class InitLibPng : public LMutex { LAutoPtr Png; public: LibPng *Get() { if (Lock(_FL)) { if (!Png) Png.Reset(new LibPng); Unlock(); } return Png; } InitLibPng() : LMutex("InitLibPng") { } } CurrentLibPng; #else #define LIBPNG #endif class GdcPng : public LFilter { static char PngSig[]; friend void PNGAPI LibPngError(png_structp Png, png_const_charp Msg); friend void PNGAPI LibPngWarning(png_structp Png, png_const_charp Msg); #if LIBPNG_SHARED LibPng *Lib; #endif int Pos; uchar *PrevScanLine; LSurface *pDC; LMemQueue DataPipe; jmp_buf Here; public: GdcPng ( #if LIBPNG_SHARED LibPng *lib #endif ); ~GdcPng(); const char *GetClass() override { return "GdcPng"; } const char *GetComponentName() override { return "libpng"; } Format GetFormat() override { return FmtPng; } void SetMeter(int i) { if (Meter) Meter->Value(i); } int GetCapabilites() override { return FILTER_CAP_READ | FILTER_CAP_WRITE; } IoStatus ReadImage(LSurface *pDC, LStream *In) override; IoStatus WriteImage(LStream *Out, LSurface *pDC) override; bool GetVariant(const char *n, LVariant &v, const char *a = NULL) override { if (!_stricmp(n, LGI_FILTER_TYPE)) { v = "Png"; // Portable Network Graphic } else if (!_stricmp(n, LGI_FILTER_EXTENSIONS)) { v = "PNG"; } else return false; return true; } }; // Object Factory class GdcPngFactory : public LFilterFactory { bool CheckFile(const char *File, int Access, const uchar *Hint) { if (Hint) { return Hint[1] == 'P' && Hint[2] == 'N' && Hint[3] == 'G'; } else { return (File) ? stristr(File, ".png") != 0 : false; } } LFilter *NewObject() { return new GdcPng ( #if LIBPNG_SHARED CurrentLibPng.Get() #endif ); } } PngFactory; // Class impl char GdcPng::PngSig[] = { (char)137, 'P', 'N', 'G', '\r', '\n', (char)26, '\n', 0 }; GdcPng::GdcPng( #if LIBPNG_SHARED LibPng *lib #endif ) { #if LIBPNG_SHARED Lib = lib; #endif Pos = 0; PrevScanLine = 0; } GdcPng::~GdcPng() { DeleteArray(PrevScanLine); } void PNGAPI LibPngError(png_structp Png, png_const_charp Msg) { GdcPng *This = (GdcPng*) #if LIBPNG_SHARED CurrentLibPng.Get()-> #endif png_get_error_ptr(Png); if (This) { printf("Libpng Error Message='%s'\n", Msg); if (This->Props) { LVariant v; This->Props->SetValue(LGI_FILTER_ERROR, v = (char*)Msg); } longjmp(This->Here, -1); } } void PNGAPI LibPngWarning(png_structp Png, png_const_charp Msg) { LgiTrace("LibPng Warning: %s\n", Msg); } void PNGAPI LibPngRead(png_structp Png, png_bytep Ptr, png_size_t Size) { LStream *s = (LStream*) #if LIBPNG_SHARED CurrentLibPng.Get()-> #endif png_get_io_ptr(Png); if (s) { s->Read(Ptr, Size); } else { LgiTrace("%s:%i - No this ptr? (%p)\n", __FILE__, __LINE__, Ptr); LAssert(0); } } struct PngWriteInfo { LStream *s; Progress *m; }; void PNGAPI LibPngWrite(png_structp Png, png_bytep Ptr, png_size_t Size) { PngWriteInfo *i = (PngWriteInfo*) #if LIBPNG_SHARED CurrentLibPng.Get()-> #endif png_get_io_ptr(Png); if (i) { i->s->Write(Ptr, Size); /* if (i->m) i->m->Value(Png->flush_rows); */ } else { LgiTrace("%s:%i - No this ptr?\n", __FILE__, __LINE__); LAssert(0); } } template void Read32_16(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 3; o->g = i->g >> 2; o->b = i->b >> 3; o++; i++; } } template void Read64_16(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 11; o->g = i->g >> 10; o->b = i->b >> 11; o++; i++; } } template void Read32_24(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r; o->g = i->g; o->b = i->b; o++; i++; } } template void Read64_24(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 8; o->g = i->g >> 8; o->b = i->b >> 8; o++; i++; } } template void Read32_32(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r; o->g = i->g; o->b = i->b; o->a = 255; o++; i++; } } template void Read64_32(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 8; o->g = i->g >> 8; o->b = i->b >> 8; o->a = 255; o++; i++; } } template void ReadAlpha32_16(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 3; o->g = i->g >> 2; o->b = i->b >> 3; o++; i++; } } template void ReadAlpha64_16(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 11; o->g = i->g >> 10; o->b = i->b >> 11; o++; i++; } } template void ReadAlpha32_24(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r; o->g = i->g; o->b = i->b; o++; i++; } } template void ReadAlpha64_24(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 8; o->g = i->g >> 8; o->b = i->b >> 8; o++; i++; } } template void ReadAlpha32_32(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r; o->g = i->g; o->b = i->b; o->a = i->a; o++; i++; } } template void ReadAlpha64_32(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 8; o->g = i->g >> 8; o->b = i->b >> 8; o->a = i->a >> 8; o++; i++; } } LFilter::IoStatus GdcPng::ReadImage(LSurface *pDeviceContext, LStream *In) { LFilter::IoStatus Status = IoError; Pos = 0; pDC = pDeviceContext; DeleteArray(PrevScanLine); if (!pDC) { LAssert(!"No DC."); return Status; } LVariant v; #if LIBPNG_SHARED if (!Lib->IsLoaded() && !Lib->Load(sLibrary)) { LString s; s.Printf("libpng is missing (%s.%s)", sLibrary, LGI_LIBRARY_EXT); if (Props) Props->SetValue(LGI_FILTER_ERROR, v = s); else LgiTrace("%s:%i - %s\n", _FL, s.Get()); static bool Warn = true; if (Warn) { LgiTrace("%s:%i - Unable to load libpng (%s.%s).\n", _FL, sLibrary, LGI_LIBRARY_EXT); Warn = false; } return LFilter::IoComponentMissing; } #endif png_structp png_ptr = NULL; if (setjmp(Here)) { return Status; } png_ptr = LIBPNG png_create_read_struct(PNG_LIBPNG_VER_STRING, (void*)this, LibPngError, LibPngWarning); if (!png_ptr) { if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "png_create_read_struct failed."); } else { png_infop info_ptr = LIBPNG png_create_info_struct(png_ptr); if (info_ptr) { LIBPNG png_set_read_fn(png_ptr, In, LibPngRead); #if 0 // What was this for again? int off = (char*)&png_ptr->io_ptr - (char*)png_ptr; if (!png_ptr->io_ptr) { printf("io_ptr offset = %i\n", off); LAssert(0); CurrentLibPng = 0; return false; } #endif LIBPNG png_read_png(png_ptr, info_ptr, 0, 0); png_bytepp Scan0 = LIBPNG png_get_rows(png_ptr, info_ptr); if (Scan0) { int BitDepth = LIBPNG png_get_bit_depth(png_ptr, info_ptr); int FinalBits = BitDepth == 16 ? 8 : BitDepth; int ColourType = LIBPNG png_get_color_type(png_ptr, info_ptr); int Channels = LIBPNG png_get_channels(png_ptr, info_ptr); int RequestBits = FinalBits * Channels; LColourSpace InCs = ColourType == PNG_COLOR_TYPE_GRAY_ALPHA ? CsIndex8 : LBitsToColourSpace(MAX(RequestBits, 8)); if (!pDC->Create( LIBPNG png_get_image_width(png_ptr, info_ptr), LIBPNG png_get_image_height(png_ptr, info_ptr), InCs, LSurface::SurfaceRequireExactCs)) { printf("%s:%i - LMemDC::Create(%i, %i, %i) failed.\n", _FL, LIBPNG png_get_image_width(png_ptr, info_ptr), LIBPNG png_get_image_height(png_ptr, info_ptr), RequestBits); } else { bool Error = false; #if 1 if (ColourType == PNG_COLOR_TYPE_GRAY_ALPHA) { pDC->HasAlpha(true); // Setup alpha channel } /* printf("PngRead %s->%s\n", LColourSpaceToString(InCs), LColourSpaceToString(pDC->GetColourSpace())); */ #endif // Copy in the scanlines int ActualBits = pDC->GetBits(); int ScanLen = LIBPNG png_get_image_width(png_ptr, info_ptr) * ActualBits / 8; LColourSpace OutCs = pDC->GetColourSpace(); for (int y=0; yY() && !Error; y++) { uchar *Scan = (*pDC)[y]; LAssert(Scan != NULL); switch (RequestBits) { case 1: { uchar *o = Scan; uchar *e = Scan + pDC->X(); uchar *i = Scan0[y]; uchar Mask = 0x80; while (o < e) { *o++ = (*i & Mask) ? 1 : 0; Mask >>= 1; if (!Mask) { i++; Mask = 0x80; } } break; } case 2: { uchar *i = Scan0[y]; uchar *o = Scan; for (int x=0; xX(); x++) { switch (x & 3) { case 0: *o++ = (*i >> 6) & 0x3; break; case 1: *o++ = (*i >> 4) & 0x3; break; case 2: *o++ = (*i >> 2) & 0x3; break; case 3: *o++ = (*i++ >> 0) & 0x3; break; } } break; } case 4: { uchar *i = Scan0[y]; uchar *o = Scan; for (int x=0; xX(); x++) { if (x & 1) *o++ = *i++ & 0xf; else *o++ = (*i >> 4) & 0xf; } break; } case 8: { memcpy(Scan, Scan0[y], ScanLen); break; } case 16: { if (ColourType == PNG_COLOR_TYPE_GRAY_ALPHA) { uint8_t *grey = Scan; uint8_t *alpha = (*(pDC->AlphaDC()))[y]; LAssert(grey && alpha); uint8_t *end = grey + pDC->X(); uint8_t *in = Scan0[y]; while (grey < end) { *grey++ = *in++; *alpha++ = *in++; } } else { memcpy(Scan, Scan0[y], ScanLen); } break; } case 24: { switch (OutCs) { #define Read24Case(name, bits) \ case Cs##name: \ { \ if (LIBPNG png_get_bit_depth(png_ptr, info_ptr) == 16) \ Read64_##bits((L##name*)Scan, (Png48*)Scan0[y], pDC->X()); \ else \ Read32_##bits((L##name*)Scan, (Png24*)Scan0[y], pDC->X()); \ break; \ } Read24Case(Rgb16, 16); Read24Case(Bgr16, 16); Read24Case(Rgb24, 24); Read24Case(Bgr24, 24); Read24Case(Xrgb32, 24); Read24Case(Rgbx32, 24); Read24Case(Xbgr32, 24); Read24Case(Bgrx32, 24); Read24Case(Rgba32, 32); Read24Case(Bgra32, 32); Read24Case(Argb32, 32); Read24Case(Abgr32, 32); default: LgiTrace("%s:%i - Unsupported colour space: 0x%x (%s)\n", _FL, pDC->GetColourSpace(), LColourSpaceToString(pDC->GetColourSpace())); LAssert(!"Not impl."); break; } break; } case 32: { switch (pDC->GetColourSpace()) { #define Read32Case(name, bits) \ case Cs##name: \ { \ if (LIBPNG png_get_bit_depth(png_ptr, info_ptr) == 16) \ ReadAlpha64_##bits((L##name*)Scan, (Png64*)Scan0[y], pDC->X()); \ else \ ReadAlpha32_##bits((L##name*)Scan, (Png32*)Scan0[y], pDC->X()); \ break; \ } Read32Case(Rgb16, 16); Read32Case(Bgr16, 16); Read32Case(Rgb24, 24); Read32Case(Bgr24, 24); Read32Case(Xrgb32, 24); Read32Case(Rgbx32, 24); Read32Case(Xbgr32, 24); Read32Case(Bgrx32, 24); Read32Case(Rgba32, 32); Read32Case(Bgra32, 32); Read32Case(Argb32, 32); Read32Case(Abgr32, 32); default: LgiTrace("%s:%i - Unsupported colour space: 0x%x (%s)\n", _FL, pDC->GetColourSpace(), LColourSpaceToString(pDC->GetColourSpace())); LAssert(!"Not impl."); if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "Missing scan convertor"); Error = true; break; } break; } default: { if (ActualBits == RequestBits) { memcpy(Scan, Scan0[y], ScanLen); } else { LAssert(!"Yeah you need to impl a convertor here."); if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "Missing scan convertor"); Error = true; } break; } } } if (RequestBits == 32) { // bool IsPreMul = pDC->IsPreMultipliedAlpha(); pDC->ConvertPreMulAlpha(true); } if (ActualBits <= 8) { // Copy in the palette png_colorp pal; int num_pal = 0; if (LIBPNG png_get_PLTE(png_ptr, info_ptr, &pal, &num_pal) == PNG_INFO_PLTE) { LPalette *Pal = new LPalette(0, num_pal); if (Pal) { for (int i=0; ir = pal[i].red; Rgb->g = pal[i].green; Rgb->b = pal[i].blue; } } pDC->Palette(Pal, true); } } if (LIBPNG png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { png_bytep trans_alpha = NULL; png_color_16p trans_color; int num_trans; if (LIBPNG png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, &trans_color)) { pDC->HasAlpha(true); LSurface *Alpha = pDC->AlphaDC(); if (Alpha) { if (trans_alpha) { for (int y=0; yY(); y++) { uchar *a = (*Alpha)[y]; uchar *p = (*pDC)[y]; for (int x=0; xX(); x++) { if (p[x] < num_trans) { a[x] = trans_alpha[p[x]]; } else { a[x] = 0xff; } } } } else if (trans_color) { for (int y=0; yY(); y++) { uchar *a = (*Alpha)[y]; uchar *p = (*pDC)[y]; for (int x=0; xX(); x++) { a[x] = p[x] == trans_color->index ? 0x00 : 0xff; } } } } else { printf("%s:%i - No alpha channel.\n", _FL); } } else { printf("%s:%i - Bad trans ptr.\n", _FL); } } } Status = Error ? IoError : IoSuccess; } } else LgiTrace("%s:%i - png_get_rows failed.\n", _FL); LIBPNG png_destroy_info_struct(png_ptr, &info_ptr); } png_charp ProfName = 0; int CompressionType = 0; png_bytep ColProf = 0; png_uint_32 ColProfLen = 0; if (LIBPNG png_get_iCCP(png_ptr, info_ptr, &ProfName, &CompressionType, &ColProf, &ColProfLen) && Props) { v.SetBinary(ColProfLen, ColProf); Props->SetValue(LGI_FILTER_COLOUR_PROF, v); } LIBPNG png_destroy_read_struct(&png_ptr, 0, 0); } return Status; } LFilter::IoStatus GdcPng::WriteImage(LStream *Out, LSurface *pDC) { LFilter::IoStatus Status = IoError; LVariant Transparent; bool HasTransparency = false; COLOUR Back = 0; LVariant v; if (!pDC) return LFilter::IoError; #if LIBPNG_SHARED if (!Lib->IsLoaded() && !Lib->Load(sLibrary)) { static bool Warn = true; if (Warn) { LgiTrace("%s:%i - Unabled to load libpng.\n", _FL); Warn = false; } return LFilter::IoComponentMissing; } #endif // Work out whether the image has transparency if (pDC->GetBits() == 32) { // Check alpha channel for (int y=0; yY() && !HasTransparency; y++) { System32BitPixel *p = (System32BitPixel*)(*pDC)[y]; if (!p) break; System32BitPixel *e = p + pDC->X(); while (p < e) { if (p->a < 255) { HasTransparency = true; break; } p++; } } } else if (pDC->AlphaDC()) { LSurface *a = pDC->AlphaDC(); if (a) { for (int y=0; yY() && !HasTransparency; y++) { uint8_t *p = (*a)[y]; if (!p) break; uint8_t *e = p + a->X(); while (p < e) { if (*p < 255) { HasTransparency = true; break; } p++; } } } } if (Props) { if (Props->GetValue(LGI_FILTER_BACKGROUND, v)) Back = v.CastInt32(); Props->GetValue(LGI_FILTER_TRANSPARENT, Transparent); } if (setjmp(Here) == 0 && pDC && Out) { LVariant ColProfile; if (Props) { Props->GetValue(LGI_FILTER_COLOUR_PROF, ColProfile); } // setup png_structp png_ptr = LIBPNG png_create_write_struct( PNG_LIBPNG_VER_STRING, (void*)this, LibPngError, LibPngWarning); if (png_ptr) { png_infop info_ptr = LIBPNG png_create_info_struct(png_ptr); if (info_ptr) { Out->SetSize(0); PngWriteInfo WriteInfo; WriteInfo.s = Out; WriteInfo.m = Meter; LIBPNG png_set_write_fn(png_ptr, &WriteInfo, LibPngWrite, 0); // png_set_write_status_fn(png_ptr, write_row_callback); bool KeyAlpha = false; bool ChannelAlpha = false; LMemDC *pTemp = 0; if (pDC->AlphaDC() && HasTransparency) { pTemp = new LMemDC(pDC->X(), pDC->Y(), System32BitColourSpace); if (pTemp) { pTemp->Colour(0); pTemp->Rectangle(); pTemp->Op(GDC_ALPHA); pTemp->Blt(0, 0, pDC); pTemp->Op(GDC_SET); pDC = pTemp; ChannelAlpha = true; } } else { if (Transparent.CastInt32() && Props && Props->GetValue(LGI_FILTER_BACKGROUND, v)) { KeyAlpha = true; } } int Ar = R32(Back); int Ag = G32(Back); int Ab = B32(Back); if (pDC->GetBits() == 32) { if (!ChannelAlpha && !KeyAlpha) { for (int y=0; yY(); y++) { System32BitPixel *s = (System32BitPixel*) (*pDC)[y]; for (int x=0; xX(); x++) { if (s[x].a < 0xff) { ChannelAlpha = true; y = pDC->Y(); break; } } } } } bool ExtraAlphaChannel = ChannelAlpha || (pDC->GetBits() > 8 ? KeyAlpha : 0); int ColourType; if (pDC->GetBits() <= 8) { if (pDC->Palette()) ColourType = PNG_COLOR_TYPE_PALETTE; else ColourType = PNG_COLOR_TYPE_GRAY; } else if (ExtraAlphaChannel) { ColourType = PNG_COLOR_TYPE_RGB_ALPHA; } else { ColourType = PNG_COLOR_TYPE_RGB; } LIBPNG png_set_IHDR(png_ptr, info_ptr, pDC->X(), pDC->Y(), 8, ColourType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); if (ColProfile.Type == GV_BINARY) { LIBPNG png_set_iCCP(png_ptr, info_ptr, (png_charp)"ColourProfile", 0, (png_const_bytep) ColProfile.Value.Binary.Data, (png_uint_32) ColProfile.Value.Binary.Length); } int TempLine = pDC->X() * ((pDC->GetBits() <= 8 ? 1 : 3) + (ExtraAlphaChannel ? 1 : 0)); uchar *TempBits = new uchar[pDC->Y() * TempLine]; if (Meter) Meter->SetRange(pDC->Y()); switch (pDC->GetBits()) { case 8: { // Output the palette LPalette *Pal = pDC->Palette(); if (Pal) { int Colours = Pal->GetSize(); LAutoPtr PngPal(new png_color[Colours]); if (PngPal) { for (int i=0; ir; PngPal[i].green = Rgb->g; PngPal[i].blue = Rgb->b; } } LIBPNG png_set_PLTE(png_ptr, info_ptr, PngPal, Colours); } } // Copy the pixels for (int y=0; yY(); y++) { uchar *s = (*pDC)[y]; Png8 *d = (Png8*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { *d++ = *s++; } } // Setup the transparent palette entry if (KeyAlpha) { static png_byte Trans[256]; for (uint n=0; nbit_depth = 8; //info_ptr->channels = 3 + (ExtraAlphaChannel ? 1 : 0); //info_ptr->color_type = PNG_COLOR_TYPE_RGB | (KeyAlpha ? PNG_COLOR_MASK_ALPHA : 0); for (int y=0; yY(); y++) { uint16 *s = (uint16*) (*pDC)[y]; if (pDC->GetBits() == 15) { if (KeyAlpha) { Png32 *d = (Png32*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = Rc15(*s); d->g = Gc15(*s); d->b = Bc15(*s); d->a = (d->r == Ar && d->g == Ag && d->b == Ab) ? 0 : 0xff; s++; d++; } } else { Png24 *d = (Png24*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = Rc15(*s); d->g = Gc15(*s); d->b = Bc15(*s); s++; d++; } } } else { if (KeyAlpha) { Png32 *d = (Png32*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = Rc16(*s); d->g = Gc16(*s); d->b = Bc16(*s); d->a = (d->r == Ar && d->g == Ag && d->b == Ab) ? 0 : 0xff; s++; d++; } } else { Png24 *d = (Png24*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = Rc16(*s); d->g = Gc16(*s); d->b = Bc16(*s); s++; d++; } } } } break; } case 24: { //info_ptr->bit_depth = 8; //info_ptr->channels = 3 + (KeyAlpha ? 1 : 0); //info_ptr->color_type = PNG_COLOR_TYPE_RGB | (KeyAlpha ? PNG_COLOR_MASK_ALPHA : 0); for (int y=0; yY(); y++) { System24BitPixel *s = (System24BitPixel*) (*pDC)[y]; if (KeyAlpha) { Png32 *d = (Png32*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = s->r; d->g = s->g; d->b = s->b; d->a = (s->r == Ar && s->g == Ag && s->b == Ab) ? 0 : 0xff; s++; d++; } } else { Png24 *d = (Png24*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = s->r; d->g = s->g; d->b = s->b; s++; d++; } } } break; } case 32: { //info_ptr->bit_depth = 8; //info_ptr->channels = 3 + (ExtraAlphaChannel ? 1 : 0); //info_ptr->color_type = PNG_COLOR_TYPE_RGB | (ExtraAlphaChannel ? PNG_COLOR_MASK_ALPHA : 0); for (int y=0; yY(); y++) { System32BitPixel *s = (System32BitPixel*) (*pDC)[y]; if (ChannelAlpha) { Png32 *d = (Png32*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = s->r; d->g = s->g; d->b = s->b; d->a = s->a; s++; d++; } } else if (KeyAlpha) { Png32 *d = (Png32*) (TempBits + (TempLine * y)); Png32 *e = d + pDC->X(); while (d < e) { if (s->a == 0 || (s->r == Ar && s->g == Ag && s->b == Ab) ) { d->r = 0; d->g = 0; d->b = 0; d->a = 0; } else { d->r = s->r; d->g = s->g; d->b = s->b; d->a = s->a; } s++; d++; } } else { Png24 *d = (Png24*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = s->r; d->g = s->g; d->b = s->b; s++; d++; } } } break; } default: { goto CleanUp; } } LArray row; if (row.Length(pDC->Y())) { for (int y=0; yY(); y++) { row[y] = TempBits + (TempLine * y); } LIBPNG png_set_rows(png_ptr, info_ptr, row.AddressOf()); LIBPNG png_write_png(png_ptr, info_ptr, 0, 0); Status = IoSuccess; } DeleteArray(TempBits); DeleteObj(pTemp); LIBPNG png_destroy_info_struct(png_ptr, &info_ptr); } CleanUp: LIBPNG png_destroy_write_struct(&png_ptr, NULL); } } return Status; } diff --git a/src/posix/SubProcess.cpp b/src/posix/SubProcess.cpp --- a/src/posix/SubProcess.cpp +++ b/src/posix/SubProcess.cpp @@ -1,763 +1,771 @@ /** \file \brief Sub-process wrapper. This class runs one or more sub-processes chained together by pipes. Example: LSubProcess p1("ls", "-l"); LSubProcess p2("grep", "string"); p1.Connect(&p2); p1.Start(true, false); int r; char Buf[256]; while ((r = p1.Read(Buf, sizeof(Buf))) > 0) { // So something with 'Buf' } */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/SubProcess.h" #define DEBUG_SUBPROCESS 0 #define DEBUG_ARGS 0 #define NULL_PIPE -1 #define ClosePipe close #define INVALID_PID -1 #include #if defined(LINUX) // !mac and !haiku #include #endif #if !defined(MAC) #include #endif #ifdef HAIKU #include #endif LSubProcess::Pipe::Pipe() { Read = Write = NULL_PIPE; } bool LSubProcess::Pipe::Create(void *UnusedParam) { return pipe(Handles) != NULL_PIPE; } void LSubProcess::Pipe::Close() { if (Read != NULL_PIPE) { ClosePipe(Read); Read = NULL_PIPE; } if (Write != NULL_PIPE) { ClosePipe(Write); Write = NULL_PIPE; } } char *ArgTok(const char *&s) { if (!s || !*s) return NULL; while (*s && strchr(LWhiteSpace, *s)) s++; if (*s == '\'' || *s == '\"') { char delim = *s++; const char *start = s; while (*s && *s != delim) s++; char *r = NewStr(start, s - start); if (*s) s++; return r; } else { const char *start = s; while (*s && !strchr(LWhiteSpace, *s)) s++; return NewStr(start, s - start); } } struct LSubProcessPriv { LString Exe; LString InitialFolder; LArray Args; bool NewGroup = false; bool PseudoConsole = false; bool EnvironmentChanged = false; LArray Environment; uint32_t ErrorCode = 0; LSubProcess::PipeHandle ExternIn = NULL_PIPE, ExternOut = NULL_PIPE; LSubProcess::ProcessId ChildPid = INVALID_PID; LSubProcess::Pipe Io; int ExitValue = -1; // was uint32 int UserId = -1; int GrpId = -1; LSubProcessPriv(bool pseudoConsole) { } ~LSubProcessPriv() { Args.DeleteArrays(); } }; LSubProcess::LSubProcess(const char *exe, const char *args, bool pseudoConsole) { d = new LSubProcessPriv(pseudoConsole); Parent = Child = NULL; d->Exe = exe; d->Args.Add(NewStr(d->Exe)); #if DEBUG_SUBPROCESS LgiTrace("%s:%i - %p::LSubProcess('%s','%s')\n", _FL, this, exe, args); #endif char *s; while ((s = ArgTok(args))) { d->Args.Add(s); #if DEBUG_ARGS LgiTrace("a='%s'\n", s); #endif } } LSubProcess::~LSubProcess() { d->Io.Close(); if (Child) { LAssert(Child->Parent == this); Child->Parent = NULL; } if (Parent) { LAssert(Parent->Child == this); Parent->Child = NULL; } DeleteObj(d); } extern char **environ; bool LSubProcess::GetNewGroup() { return d->NewGroup; } void LSubProcess::SetNewGroup(bool ng) { d->NewGroup = ng; } LSubProcess::ProcessId LSubProcess::Handle() { return d->ChildPid; } LSubProcess::Variable *LSubProcess::GetEnvVar(const char *Var, bool Create) { if (d->Environment.Length() == 0) { // Read all variables in for (int i=0; environ[i]; i++) { auto p = LString(environ[i]).Split("=", 1); if (p.Length() == 2) { Variable &v = d->Environment.New(); v.Var = p[0]; v.Val = p[1]; } } } for (unsigned i=0; iEnvironment.Length(); i++) { if (d->Environment[i].Var.Equals(Var)) return &d->Environment[i]; } if (Create) { Variable &v = d->Environment.New(); v.Var = Var; return &v; } return NULL; } bool LSubProcess::Dupe(PipeHandle Old, PipeHandle New) { while ((dup2(Old, New) == -1) && (errno == EINTR)) ; return true; } bool LSubProcess::IsRunning() { int Status = 0; pid_t r = waitpid(d->ChildPid, &Status, WNOHANG); if (r == d->ChildPid) { d->ChildPid = INVALID_PID; if (WIFEXITED(Status)) d->ExitValue = WEXITSTATUS(Status); else d->ExitValue = 255; } return d->ChildPid != INVALID_PID; } uint32_t LSubProcess::GetErrorCode() { return d->ErrorCode; } int32 LSubProcess::GetExitValue() { if (d->ChildPid != INVALID_PID) // This will set ExitValue if the process has finished. IsRunning(); return d->ExitValue; } bool LSubProcess::SetUser(const char *User, const char *Pass) { if (!User || !Pass) { LgiTrace("%s:%i - SetUser param err.\n", _FL); return false; } auto entry = getpwnam(User); if (!entry) { LgiTrace("%s:%i - SetUser: User '%s' doesn't exist.\n", _FL, User); return false; } if (0 != strcmp(entry->pw_passwd, "x")) { if (strcmp(entry->pw_passwd, crypt(Pass, entry->pw_passwd))) { LgiTrace("%s:%i - SetUser: Wrong password.\n", _FL); return false; } } #if !defined(MAC) else { // password is in shadow file auto shadowEntry = getspnam(User); if (!shadowEntry) { LgiTrace("%s:%i - SetUser: Failed to read shadow entry for user '%s'\n", User); return false; } if (strcmp(shadowEntry->sp_pwdp, crypt(Pass, shadowEntry->sp_pwdp))) { LgiTrace("%s:%i - SetUser: Wrong password.\n", _FL); return false; } } #endif d->UserId = entry->pw_uid; d->GrpId = entry->pw_gid; return true; } void LSubProcess::SetInitFolder(const char *f) { d->InitialFolder = f; } const char *LSubProcess::GetEnvironment(const char *Var) { Variable *v = GetEnvVar(Var); return v ? v->Val.Get() : NULL; } bool LSubProcess::SetEnvironment(const char *Var, const char *Value) { Variable *v = GetEnvVar(Var, true); if (!v) return false; bool IsPath = !_stricmp(Var, "PATH"); LStringPipe a; const char *s = Value; while (*s) { char *n = strchr(s, '%'); char *e = n ? strchr(n + 1, '%') : NULL; if (n && e) { a.Write(s, (int) (n-s)); n++; ptrdiff_t bytes = e - n; char Name[128]; if (bytes > sizeof(Name) - 1) bytes = sizeof(Name)-1; memcpy(Name, n, bytes); Name[bytes] = 0; const char *existing = GetEnvironment(Name); if (existing) { a.Write(existing, (int)strlen(existing)); } s = e + 1; } else { a.Write(s, (int)strlen(s)); break; } } v->Val = a.NewLStr(); if (IsPath) { // Remove missing paths from the list auto t = LString(v->Val).SplitDelimit(LGI_PATH_SEPARATOR); LStringPipe p; for (unsigned i=0; iVal = p.NewLStr(); } d->EnvironmentChanged = true; return true; } bool LSubProcess::GetValue(const char *Var, ::LVariant &Value) { /* switch (LStringToDomProp(Var)) { case StreamReadable: { break; } case StreamWritable: { break; } default: return false; } */ return false; } void LSubProcess::SetStdin(OsFile Hnd) { d->ExternIn = Hnd; } void LSubProcess::SetStdout(OsFile Hnd) { d->ExternOut = Hnd; } void LSubProcess::Connect(LSubProcess *child) { Child = child; if (Child) { Child->Parent = this; } } bool LSubProcess::Start(bool ReadAccess, bool WriteAccess, bool MapStderrToStdout) { bool Status = false; #if DEBUG_SUBPROCESS LgiTrace("%s:%i - %p::Start(%i,%i,%i)\n", _FL, this, ReadAccess, WriteAccess, MapStderrToStdout); #endif int in[2]; if (pipe(in) == -1) { printf("parent: Failed to create stdin pipe"); return false; } int out[2]; if (pipe(out) == -1) { printf("parent: Failed to create stdout pipe"); return false; } d->ChildPid = fork(); if (d->ChildPid == 0) { // We are in the child process. if (d->InitialFolder) { chdir(d->InitialFolder); } // Child shouldn't write to its stdin. if (close(in[1])) printf("%s:%i - close failed.\n", _FL); // Child shouldn't read from its stdout. if (close(out[0])) printf("%s:%i - close failed.\n", _FL); // Redirect stdin and stdout for the child process. if (dup2(in[0], fileno(stdin)) == -1) { printf("%s:%i - child[pre-exec]: Failed to redirect stdin for child\n", _FL); return false; } if (close(in[0])) printf("%s:%i - close failed.\n", _FL); if (dup2(out[1], fileno(stdout)) == -1) { printf("%s:%i - child[pre-exec]: Failed to redirect stdout for child\n", _FL); return false; } if (dup2(out[1], fileno(stderr)) == -1) { printf("%s:%i - child[pre-exec]: Failed to redirect stderr for child\n", _FL); return false; } close(out[1]); // Execute the child d->Args.Add(NULL); LString::Array Path; if (!LFileExists(d->Exe)) { // Apparently 'execve' doesn't search the path... so we're going to look up the // full executable path ourselves. if (!Path.Length()) Path = LGetPath(); for (auto s: Path) { LFile::Path p(s, d->Exe); if (p.Exists()) { d->Exe = p.GetFull(); break; } } } if (d->UserId >= 0) setuid(d->UserId); if (d->GrpId >= 0) setgid(d->GrpId); if (d->Environment.Length()) { LString::Array Vars; LArray Env; Vars.SetFixedLength(false); for (auto v : d->Environment) { LString &s = Vars.New(); s.Printf("%s=%s", v.Var.Get(), v.Val.Get()); Env.Add(s.Get()); if (v.Var.Equals("PATH")) Path = v.Val.Split(LGI_PATH_SEPARATOR); } Env.Add(NULL); #if DEBUG_SUBPROCESS printf("Exe=%s\n", d->Exe.Get()); printf("Env.Len=%i\n", (int)Env.Length()); for (int i=0; iExe, &d->Args[0], Env.AddressOf()); printf("execve=%i err=%i\n", r, errno); } else { execvp(d->Exe, &d->Args[0]); } // Execution will pass to here if the 'Exe' can't run or doesn't exist // So by exiting with an error the parent process can handle it. - printf("GSUBPROCESS_ERROR\n"); - exit(GSUBPROCESS_ERROR); + printf("LSUBPROCESS_ERROR\n"); + + #ifdef MAC + // While 'exit' would be nice and clean it does cause crashes in free the global InitLibPng object + // We HAVE to call exec??? to replace the process... anything will do... 'ls' will just quit quickly + char *a= {0}; + execv("/bin/ls", &a); + #else + exit(LSUBPROCESS_ERROR); + #endif } else { // We are in the parent process. if (d->ChildPid == -1) { printf("%s:%i - parent: Failed to create child", _FL); return false; } // Parent shouldn't read from child's stdin. if (close(in[0])) printf("%s:%i - close failed.\n", _FL); // Parent shouldn't write to child's stdout. if (close(out[1])) printf("%s:%i - close failed.\n", _FL); d->Io.Read = out[0]; d->Io.Write = in[1]; #if DEBUG_SUBPROCESS printf("USE_SIMPLE_FORK success.\n"); #endif return true; } return Status; } int32 LSubProcess::Communicate(LStreamI *Out, LStreamI *In, LCancel *Cancel) { char Buf[1024]; ssize_t r; LAssert(In == NULL); // Impl me. #define NOT_CANCELLED (!Cancel || !Cancel->IsCancelled()) while (IsRunning() && NOT_CANCELLED) { r = Read(Buf, sizeof(Buf)); if (r > 0 && Out) Out->Write(Buf, r); } while (NOT_CANCELLED) { r = Read(Buf, sizeof(Buf)); if (r > 0 && Out) Out->Write(Buf, r); else break; } return GetExitValue(); } int LSubProcess::Wait() { int Status = -1; if (d->ChildPid != INVALID_PID) { int Status = 0; pid_t r = waitpid(d->ChildPid, &Status, 0); if (r == d->ChildPid) { d->ChildPid = INVALID_PID; if (WIFEXITED(Status)) d->ExitValue = WEXITSTATUS(Status); else d->ExitValue = 255; } } return Status; } bool LSubProcess::Interrupt() { return Signal(SIGINT); } bool LSubProcess::Signal(int which) { if (d->ChildPid == INVALID_PID) { printf("%s:%i - child pid doesn't exist (%s).\n", _FL, d->Exe.Get()); return false; } if (kill(d->ChildPid, which)) { printf("%s:%i - kill(%i, %i) failed.\n", _FL, d->ChildPid, which); return false; } printf("%s:%i - kill(%i, %i).\n", _FL, d->ChildPid, which); if (which == SIGTERM) d->ChildPid = INVALID_PID; return true; } bool LSubProcess::Kill() { return Signal(SIGTERM); } LString LSubProcess::Read() { LStringPipe p(512); char Buf[512]; ssize_t Rd; while (Peek()) { Rd = Read(Buf, sizeof(Buf)); if (Rd > 0) p.Write(Buf, Rd); else break; } return p.NewLStr(); } ssize_t LSubProcess::Read(void *Buf, ssize_t Size, int TimeoutMs) { bool DoRead = true; if (TimeoutMs) { OsSocket s = d->Io.Read; if (ValidSocket(s)) { struct timeval t = {TimeoutMs / 1000, (TimeoutMs % 1000) * 1000}; fd_set r; FD_ZERO(&r); FD_SET(s, &r); int v = select((int)s+1, &r, 0, 0, &t); if (v > 0 && FD_ISSET(s, &r)) { DoRead = true; } else { // printf("SubProc not readable..\n"); return 0; } } else LgiTrace("%s:%i - Invalid socket.\n", _FL); } return (int)read(d->Io.Read, Buf, Size); } int LSubProcess::Peek() { int bytesAvailable = 0; int r = ioctl(d->Io.Read, FIONREAD, &bytesAvailable); return r ? -1 : bytesAvailable; } bool LSubProcess::Write(LString s) { auto Wr = Write(s.Get(), s.Length()); return Wr == s.Length(); } ssize_t LSubProcess::Write(const void *Buf, ssize_t Size, int Flags) { return (int)write(d->Io.Write, Buf, Size); } //////////////////////////////////////////////////////////////////////////////////// bool LIsProcess(OsProcessId Pid) { bool Status = false; #if defined(MAC) #if LGI_COCOA #ifndef __OBJC__ #error "Change this source code to ObjC++" #endif auto app = [NSRunningApplication runningApplicationWithProcessIdentifier: Pid]; return app != nil; #elif LGI_CARBON ProcessSerialNumber psn; OSStatus e = GetProcessForPID(Pid, &psn); return e == 0; #else #warning FIXME #endif #elif defined(LINUX) char ProcPath[256]; sprintf_s(ProcPath, sizeof(ProcPath), "/proc/%i", Pid); Status = LDirExists(ProcPath); #elif defined HAIKU BRoster r; app_info a; if (r.GetRunningAppInfo(Pid, &a) == B_OK) { Status = true; } #else #error Impl me. #endif return Status; }