diff --git a/Ide/src/IdeProject.cpp b/Ide/src/IdeProject.cpp --- a/Ide/src/IdeProject.cpp +++ b/Ide/src/IdeProject.cpp @@ -1,4112 +1,4119 @@ #if defined(WIN32) #include #else #include #endif #include #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Token.h" #include "lgi/common/Combo.h" #include "lgi/common/Net.h" #include "lgi/common/ListItemCheckBox.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/DropFiles.h" #include "lgi/common/SubProcess.h" #include "lgi/common/Css.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Button.h" #include "lgi/common/RegKey.h" #include "lgi/common/FileSelect.h" #include "lgi/common/Menu.h" #include "LgiIde.h" #include "resdefs.h" #include "FtpThread.h" #include "ProjectNode.h" #include "WebFldDlg.h" extern const char *Untitled; const char SourcePatterns[] = "*.c;*.h;*.cpp;*.cc;*.java;*.d;*.php;*.html;*.css;*.js"; const char *AddFilesProgress::DefaultExt = "c,cpp,cc,cxx,h,hpp,hxx,html,css,json,js,jsx,txt,png,jpg,jpeg,rc,xml,mk,paths,makefile,py,java,php"; const char *VsBinaries[] = {"devenv.com", "WDExpress.exe"}; #define USE_OPEN_PROGRESS 1 #define STOP_BUILD_TIMEOUT 2000 #ifdef WINDOWS #define LGI_STATIC_LIBRARY_EXT "lib" #else #define LGI_STATIC_LIBRARY_EXT "a" #endif const char *PlatformNames[] = { "Windows", "Linux", "Mac", "Haiku", 0 }; int PlatformCtrlId[] = { IDC_WIN32, IDC_LINUX, IDC_MAC, IDC_HAIKU, 0 }; const char *PlatformDynamicLibraryExt(IdePlatform Platform) { if (Platform == PlatformWin) return "dll"; if (Platform == PlatformMac) return "dylib"; return "so"; } const char *PlatformSharedLibraryExt(IdePlatform Platform) { if (Platform == PlatformWin) return "lib"; return "a"; } const char *PlatformExecutableExt(IdePlatform Platform) { if (Platform == PlatformWin) return ".exe"; return ""; } char *ToUnixPath(char *s) { if (s) { char *c; while ((c = strchr(s, '\\'))) *c = '/'; } return s; } const char *CastEmpty(char *s) { return s ? s : ""; } bool FindInPath(LString &Exe) { LString::Array Path = LString(getenv("PATH")).Split(LGI_PATH_SEPARATOR); for (unsigned i=0; i SubProc; LString::Array BuildConfigs; LString::Array PostBuild; int AppHnd; enum CompilerType { DefaultCompiler, VisualStudio, MingW, Gcc, CrossCompiler, PythonScript, IAR, Nmake, Cygwin, Xcode, } Compiler; enum ArchType { DefaultArch, ArchX32, ArchX64, ArchArm6, ArchArm7, } Arch; public: BuildThread(IdeProject *proj, char *makefile, bool clean, BuildConfig config, bool all, int wordsize); ~BuildThread(); ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) override; LString FindExe(); LAutoString WinToMingWPath(const char *path); int Main() override; }; class IdeProjectPrivate { public: AppWnd *App; IdeProject *Project; bool Dirty, UserFileDirty; LString FileName; IdeProject *ParentProject; IdeProjectSettings Settings; LAutoPtr Thread; LHashTbl, ProjectNode*> Nodes; int NextNodeId; // Threads LAutoPtr CreateMakefile; // User info file LString UserFile; LHashTbl,int> UserNodeFlags; IdeProjectPrivate(AppWnd *a, IdeProject *project) : Project(project), Settings(project) { App = a; Dirty = false; UserFileDirty = false; ParentProject = 0; NextNodeId = 1; } void CollectAllFiles(LTreeNode *Base, LArray &Files, bool SubProjects, int Platform); }; LString ToPlatformPath(const char *s, IdePlatform platform) { LString p = s; if (platform == PlatformWin) return p.Replace("/", "\\"); else return p.Replace("\\", "/"); } class MakefileThread : public LThread, public LCancel { IdeProjectPrivate *d; IdeProject *Proj; IdePlatform Platform; LStream *Log; bool BuildAfterwards; bool HasError; public: static int Instances; MakefileThread(IdeProjectPrivate *priv, IdePlatform platform, bool Build) : LThread("MakefileThread") { Instances++; d = priv; Proj = d->Project; Platform = platform; BuildAfterwards = Build; HasError = false; Log = d->App->GetBuildLog(); Run(); } ~MakefileThread() { Cancel(); while (!IsExited()) LSleep(1); Instances--; } void OnError(const char *Fmt, ...) { va_list Arg; va_start(Arg, Fmt); LStreamPrintf(Log, 0, Fmt, Arg); va_end(Arg); HasError = true; } int Main() { const char *PlatformName = PlatformNames[Platform]; const char *PlatformLibraryExt = NULL; const char *PlatformStaticLibExt = NULL; const char *PlatformExeExt = ""; - const char *CompilerFlags = "-fPIC -fno-inline -fpermissive -Wno-format-truncation"; + const char *CCompilerFlags = "-MMD -MP -g -fPIC -fno-inline"; + const char *CppCompilerFlags = "$CFlags -fpermissive -std=c++14"; const char *TargetType = d->Settings.GetStr(ProjTargetType, NULL, Platform); const char *CompilerName = d->Settings.GetStr(ProjCompiler); LString LinkerFlags; LString CCompilerBinary = "gcc"; LString CppCompilerBinary = "g++"; LStream *Log = d->App->GetBuildLog(); bool IsExecutableTarget = TargetType && !stricmp(TargetType, "Executable"); bool IsDynamicLibrary = TargetType && !stricmp(TargetType, "DynamicLibrary"); LAssert(Log); if (!Log) return false; Log->Print("CreateMakefile for '%s'...\n", PlatformName); if (Platform == PlatformWin) { LinkerFlags = ",--enable-auto-import"; } else { if (IsDynamicLibrary) { LinkerFlags = ",-soname,$(TargetFile)"; } LinkerFlags += ",-export-dynamic,-R."; } auto Base = Proj->GetBasePath(); auto MakeFile = Proj->GetMakefile(Platform); LString MakeFilePath; Proj->CheckExists(MakeFile); if (!MakeFile) { MakeFilePath = Base.Get(); LFile::Path p(MakeFilePath, "makefile"); MakeFile = p.GetFull(); } else if (LIsRelativePath(MakeFile)) { LFile::Path p(Base); p += MakeFile; MakeFile = p.GetFull(); p--; MakeFilePath = p.GetFull(); } else { LFile::Path p(MakeFile); p--; MakeFilePath = p.GetFull(); } // LGI_LIBRARY_EXT switch (Platform) { case PlatformWin: PlatformLibraryExt = "dll"; PlatformStaticLibExt = "lib"; PlatformExeExt = ".exe"; break; case PlatformLinux: case PlatformHaiku: PlatformLibraryExt = "so"; PlatformStaticLibExt = "a"; break; case PlatformMac: PlatformLibraryExt = "dylib"; PlatformStaticLibExt = "a"; break; default: LAssert(0); break; } if (CompilerName) { if (!stricmp(CompilerName, "cross")) { LString CBin = d->Settings.GetStr(ProjCCrossCompiler, NULL, Platform); if (CBin && !LFileExists(CBin)) FindInPath(CBin); if (CBin && LFileExists(CBin)) CCompilerBinary = CBin; else Log->Print("%s:%i - Error: C cross compiler '%s' not found.\n", _FL, CBin.Get()); LString CppBin = d->Settings.GetStr(ProjCppCrossCompiler, NULL, Platform); if (CppBin && !LFileExists(CppBin)) FindInPath(CppBin); if (CppBin && LFileExists(CppBin)) CppCompilerBinary = CppBin; else Log->Print("%s:%i - Error: C++ cross compiler '%s' not found.\n", _FL, CppBin.Get()); } } LFile m; if (!m.Open(MakeFile, O_WRITE)) { Log->Print("Error: Failed to open '%s' for writing.\n", MakeFile.Get()); return false; } m.SetSize(0); m.Print("#!/usr/bin/make\n" "#\n" "# This makefile generated by LgiIde\n" "# http://www.memecode.com/lgi.php\n" "#\n" "\n" ".SILENT :\n" "\n" "CC = %s\n" "CPP = %s\n", CCompilerBinary.Get(), CppCompilerBinary.Get()); // Collect all files that require building LArray Files; d->CollectAllFiles ( Proj, Files, false, 1 << Platform ); if (IsExecutableTarget) { LString Exe = Proj->GetExecutable(Platform); if (Exe) { if (LIsRelativePath(Exe)) m.Print("Target = %s\n", ToPlatformPath(Exe, Platform).Get()); else { auto RelExe = LMakeRelativePath(Base, Exe); if (Base && RelExe) { m.Print("Target = %s\n", ToPlatformPath(RelExe, Platform).Get()); } else { Log->Print("%s:%i - Error: Missing path (%s, %s).\n", _FL, Base.Get(), RelExe.Get()); return false; } } } else { Log->Print("%s:%i - Error: No executable name specified (%s, %s).\n", _FL, TargetType, d->FileName.Get()); return false; } } else { LString Target = Proj->GetTargetName(Platform); if (Target) m.Print("Target = %s\n", ToPlatformPath(Target, Platform).Get()); else { Log->Print("%s:%i - Error: No target name specified.\n", _FL); return false; } } // Output the build mode, flags and some paths auto BuildMode = d->App->GetBuildMode(); auto BuildModeName = toString(BuildMode); m.Print("ifndef Build\n" " Build = %s\n" "endif\n", BuildModeName); m.Print("MakeDir := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))\n"); LString sDefines[BuildMax]; LString sLibs[BuildMax]; LString sIncludes[BuildMax]; const char *ExtraLinkFlags = NULL; const char *ExeFlags = NULL; if (Platform == PlatformWin) { ExtraLinkFlags = ""; ExeFlags = " -mwindows"; m.Print("BuildDir = $(Build)\n" "\n" - "Flags = %s\n", - CompilerFlags); + "CFlags = %s\n" + "CppFlags = %s\n", + CCompilerFlags, + CppCompilerFlags); const char *DefDefs = "-DWIN32 -D_REENTRANT"; sDefines[BuildDebug] = DefDefs; sDefines[BuildRelease] = DefDefs; } else { LString PlatformCap = PlatformName; ExtraLinkFlags = ""; ExeFlags = ""; m.Print("BuildDir = $(Build)\n" "\n" - "Flags = %s\n", - CompilerFlags); + "CFlags = %s\n" + "CppFlags = %s\n", + CCompilerFlags, + CppCompilerFlags); sDefines[0].Printf("-D%s -D_REENTRANT", PlatformCap.Upper().Get()); #ifdef LINUX sDefines[BuildDebug] += " -D_FILE_OFFSET_BITS=64"; // >:-( sDefines[BuildDebug] += " -DPOSIX"; #endif sDefines[BuildRelease] = sDefines[BuildDebug]; } List Deps; Proj->GetChildProjects(Deps); for (int Cfg = BuildDebug; Cfg < BuildMax; Cfg++) { // Set the config auto cfgName = toString((BuildConfig)Cfg); d->Settings.SetCurrentConfig(cfgName); // Get the defines setup auto PDefs = d->Settings.GetStr(ProjDefines, NULL, Platform); if (ValidStr(PDefs)) { LToken Defs(PDefs, " ;,\r\n"); for (int i=0; iSettings.GetStr(ProjLibraryPaths, NULL, Platform); if (ValidStr(PLibPaths)) { LString::Array LibPaths = PLibPaths.Split("\n"); for (auto i: LibPaths) { LString s, in = i.Strip(); if (!in.Length()) continue; if (strchr("`-", in(0))) { s.Printf(" \\\n\t\t%s", in.Get()); } else { LString Rel; if (!LIsRelativePath(in)) Rel = LMakeRelativePath(Base, in); LString Final = Rel ? Rel.Get() : in.Get(); if (!Proj->CheckExists(Final)) OnError("%s:%i - Library path '%s' doesn't exist (from %s).\n", _FL, Final.Get(), Proj->GetFileName()); s.Printf(" \\\n\t\t-L%s", ToUnixPath(Final)); } sLibs[Cfg] += s; } } const char *PLibs = d->Settings.GetStr(ProjLibraries, NULL, Platform); if (ValidStr(PLibs)) { LToken Libs(PLibs, "\r\n"); for (int i=0; iCheckExists(l); s.Printf(" \\\n\t\t-l%s", ToUnixPath(l)); } sLibs[Cfg] += s; } } for (auto dep: Deps) { LString Target = dep->GetTargetName(Platform); if (Target) { char t[MAX_PATH_LEN]; strcpy_s(t, sizeof(t), Target); if (!strnicmp(t, "lib", 3)) memmove(t, t + 3, strlen(t + 3) + 1); char *dot = strrchr(t, '.'); if (dot) *dot = 0; LString s, sTarget = t; Proj->CheckExists(sTarget); s.Printf(" \\\n\t\t-l%s$(Tag)", ToUnixPath(sTarget)); sLibs[Cfg] += s; auto DepBase = dep->GetBasePath(); if (DepBase) { LString DepPath = DepBase.Get(); auto Rel = LMakeRelativePath(Base, DepPath); LString Final = Rel ? Rel.Get() : DepPath.Get(); Proj->CheckExists(Final); s.Printf(" \\\n\t\t-L%s/$(BuildDir)", ToUnixPath(Final.RStrip("/\\"))); sLibs[Cfg] += s; } } } // Includes // Do include paths LHashTbl,bool> Inc; auto ProjIncludes = d->Settings.GetStr(ProjIncludePaths, NULL, Platform); if (ValidStr(ProjIncludes)) { // Add settings include paths. LToken Paths(ProjIncludes, "\r\n"); for (int i=0; iCheckExists(pn)) OnError("%s:%i - Include path '%s' doesn't exist.\n", _FL, pn.Get()); else if (!Inc.Find(pn)) Inc.Add(pn, true); } } const char *SysIncludes = d->Settings.GetStr(ProjSystemIncludes, NULL, Platform); if (ValidStr(SysIncludes)) { // Add settings include paths. LToken Paths(SysIncludes, "\r\n"); for (int i=0; iCheckExists(pn)) OnError("%s:%i - System include path '%s' doesn't exist (from %s).\n", _FL, pn.Get(), Proj->GetFileName()); else if (!Inc.Find(pn)) Inc.Add(pn, true); } } LString::Array Incs; for (auto i: Inc) Incs.New() = i.key; Incs.Sort(); for (auto i: Incs) { LString s; if (*i == '`') { // Pass though shell cmd s.Printf(" \\\n\t\t%s", i.Get()); } else { LFile::Path p; if (LIsRelativePath(i)) { p = Base.Get(); p += i; } else p = i; auto rel = LMakeRelativePath(Base, p.GetFull()); s.Printf(" \\\n\t\t-I%s", ToUnixPath(rel ? rel : i)); } sIncludes[Cfg] += s; } } // Output the defs section for Debug and Release // Debug specific m.Print("\n" "ifeq ($(Build),Debug)\n" - " Flags += -MMD -MP -g -std=c++14\n" + " CFlags += -g\n" + " CppFlags += -g\n" " Tag = d\n" " Defs = -D_DEBUG %s\n" " Libs = %s\n" " Inc = %s\n", CastEmpty(sDefines [BuildDebug].Get()), CastEmpty(sLibs [BuildDebug].Get()), CastEmpty(sIncludes[BuildDebug].Get())); // Release specific m.Print("else\n" - " Flags += -MMD -MP -s -Os -std=c++14\n" + " CFlags += -s -Os\n" + " CppFlags += -s -Os\n" " Defs = %s\n" " Libs = %s\n" " Inc = %s\n" "endif\n" "\n", CastEmpty(sDefines [BuildRelease].Get()), CastEmpty(sLibs [BuildRelease].Get()), CastEmpty(sIncludes[BuildRelease].Get())); if (Files.Length()) { LArray VPath; // Proj->BuildIncludePaths(VPath, false, false, Platform); auto AddVPath = [&VPath](LString p) { for (auto s: VPath) if (p == s) return; VPath.Add(p); }; // Do source code list... m.Print("# Dependencies\n" "Source =\t"); LString::Array SourceFiles; for (auto &n: Files) { if (n->GetType() == NodeSrc) { auto f = n->GetFileName(); auto path = ToPlatformPath(f, Platform); LFile::Path p; if (LIsRelativePath(path)) { p = Base.Get(); p += path; } else { p = path; } auto rel = LMakeRelativePath(Base, p.GetFull()); if (rel && rel.Find("./") == 0) rel = rel(2,-1); SourceFiles.Add(rel ? rel : path); if (true) { // Also add path of file to VPath. p--; AddVPath(p.GetFull()); } } } SourceFiles.Sort(); int c = 0; for (auto &src: SourceFiles) { if (c++) m.Print(" \\\n\t\t\t"); m.Print("%s", src.Get()); } m.Print("\n" "\n" "SourceC := $(filter %%.c,$(Source))\n" "ObjectsC := $(SourceC:.c=.o)\n" "SourceCpp := $(filter %%.cpp,$(Source))\n" "ObjectsCpp := $(SourceCpp:.cpp=.o)\n" "Objects := $(notdir $(ObjectsC) $(ObjectsCpp))\n" "Objects := $(addprefix $(BuildDir)/,$(Objects))\n" "Deps := $(patsubst %%.o,%%.d,$(Objects))\n" "\n" "$(BuildDir)/%%.o: %%.c\n" " mkdir -p $(@D)\n" " echo $(notdir $<) [$(Build)]\n" - " $(CC) $(Inc) $(Flags) $(Defs) -c $< -o $@\n" + " $(CC) $(Inc) $(CFlags) $(Defs) -c $< -o $@\n" "\n" "$(BuildDir)/%%.o: %%.cpp\n" " mkdir -p $(@D)\n" " echo $(notdir $<) [$(Build)]\n" - " $(CPP) $(Inc) $(Flags) $(Defs) -c $< -o $@\n" + " $(CPP) $(Inc) $(CppFlags) $(Defs) -c $< -o $@\n" "\n"); // Write out the target stuff m.Print("# Target\n"); LHashTbl,bool> DepFiles; if (TargetType) { if (IsExecutableTarget) { m.Print("# Executable target\n" "$(Target) :"); LStringPipe Rules; IdeProject *Dep; uint64 Last = LCurrentTime(); int Count = 0; auto It = Deps.begin(); for (Dep=*It; Dep && !IsCancelled(); Dep=*(++It), Count++) { // Get dependency to create it's own makefile... Dep->CreateMakefile(Platform, false); // Build a rule to make the dependency if any of the source changes... auto DepBase = Dep->GetBasePath(); auto TargetFile = Dep->GetTargetFile(Platform); if (DepBase && Base && TargetFile) { LString Rel = LMakeRelativePath(Base, DepBase); ToNativePath(Rel); // Add tag to target name auto Parts = TargetFile.SplitDelimit("."); if (Parts.Length() == 2) TargetFile.Printf("lib%s$(Tag).%s", Parts[0].Get(), Parts[1].Get()); LFile::Path Buf(Rel); Buf += "$(BuildDir)"; Buf += TargetFile; m.Print(" %s", Buf.GetFull().Get()); LArray AllDeps; Dep->GetAllDependencies(AllDeps, Platform); LAssert(AllDeps.Length() > 0); AllDeps.Sort(StrSort); Rules.Print("%s : ", Buf.GetFull().Get()); for (int i=0; iGetMakefile(Platform); if (Mk) { LString MakefileRel = LMakeRelativePath(DepBase, Mk); if (MakefileRel) { ToNativePath(MakefileRel); Rules.Print(" -f %s", MakefileRel.Get()); } else if (auto DepMakefile = strrchr(Mk, DIR_CHAR)) { Rules.Print(" -f %s", DepMakefile + 1); } } else { Mk = Dep->GetMakefile(Platform); OnError("%s:%i - No makefile for '%s'\n", _FL, Dep->GetFullPath().Get()); } Rules.Print("\n\n"); } uint64 Now = LCurrentTime(); if (Now - Last > 1000) { Last = Now; Log->Print("Building deps %i%%...\n", (int) (((int64)Count+1)*100/Deps.Length())); } } m.Print(" $(Objects)\n" " mkdir -p $(BuildDir)\n" " @echo Linking $(Target) [$(Build)]...\n" " $(CPP)%s%s %s%s -o \\\n" " $(Target) $(Objects) $(Libs)\n", ExtraLinkFlags, ExeFlags, ValidStr(LinkerFlags) ? "-Wl" : "", LinkerFlags.Get()); if (Platform == PlatformHaiku) { // Is there an application icon configured? const char *AppIcon = d->Settings.GetStr(ProjApplicationIcon, NULL, Platform); if (AppIcon) { m.Print(" addattr -f %s -t \"'VICN'\" \"BEOS:ICON\" $(Target)\n", AppIcon); } } LString PostBuildCmds = d->Settings.GetStr(ProjPostBuildCommands, NULL, Platform); if (ValidStr(PostBuildCmds)) { LString::Array a = PostBuildCmds.Split("\n"); for (unsigned i=0; iGetMakefile(Platform); if (mk) { LAutoString dep_base = d->GetBasePath(); d->CheckExists(dep_base); auto rel_dir = LMakeRelativePath(Base, dep_base); d->CheckExists(rel_dir); char *mk_leaf = strrchr(mk, DIR_CHAR); m.Print(" +make -C \"%s\" -f \"%s\" clean\n", ToUnixPath(rel_dir ? rel_dir.Get() : dep_base.Get()), ToUnixPath(mk_leaf ? mk_leaf + 1 : mk.Get())); } } m.Print("\n"); } // Shared library else if (!stricmp(TargetType, "DynamicLibrary")) { m.Print("TargetFile = lib$(Target)$(Tag).%s\n" "$(TargetFile) : $(Objects)\n" " mkdir -p $(BuildDir)\n" " @echo Linking $(TargetFile) [$(Build)]...\n" " $(CPP)$s -shared \\\n" " %s%s \\\n" " -o $(BuildDir)/$(TargetFile) \\\n" " $(Objects) \\\n" " $(Libs)\n", PlatformLibraryExt, ValidStr(ExtraLinkFlags) ? "-Wl" : "", ExtraLinkFlags, LinkerFlags.Get()); LString PostBuildCmds = d->Settings.GetStr(ProjPostBuildCommands, NULL, Platform); if (ValidStr(PostBuildCmds)) { LString::Array a = PostBuildCmds.Split("\n"); for (unsigned i=0; iSettings.GetStr(ProjPostBuildCommands, NULL, Platform); if (ValidStr(PostBuildCmds)) { LString::Array a = PostBuildCmds.Split("\n"); for (unsigned i=0; iCheckExists(p); if (p && !strchr(p, '`')) { if (!LIsRelativePath(p)) { auto a = LMakeRelativePath(Base, p); m.Print(" \\\n\t%s", ToPlatformPath(a ? a.Get() : p.Get(), Platform).Get()); } else { m.Print(" \\\n\t%s", ToPlatformPath(p, Platform).Get()); } } } m.Print("\n"); const char *OtherMakefileRules = d->Settings.GetStr(ProjMakefileRules, NULL, Platform); if (ValidStr(OtherMakefileRules)) { m.Print("\n%s\n", OtherMakefileRules); } } } else { m.Print("# No files require building.\n"); } Log->Print("...Done: '%s'\n", MakeFile.Get()); if (BuildAfterwards) { if (!Proj->GetApp()->PostEvent(M_START_BUILD)) printf("%s:%i - PostEvent(M_START_BUILD) failed.\n", _FL); } return HasError; } void OnAfterMain() { Proj->GetApp()->PostEvent(M_MAKEFILES_CREATED, (LMessage::Param)Proj); } }; ///////////////////////////////////////////////////////////////////////////////////// NodeSource::~NodeSource() { if (nView) { nView->nSrc = 0; } } NodeView::~NodeView() { if (nSrc) { nSrc->nView = 0; } } ////////////////////////////////////////////////////////////////////////////////// int NodeSort(LTreeItem *a, LTreeItem *b, NativeInt d) { ProjectNode *A = dynamic_cast(a); ProjectNode *B = dynamic_cast(b); if (A && B) { if ( (A->GetType() == NodeDir) ^ (B->GetType() == NodeDir) ) { return A->GetType() == NodeDir ? -1 : 1; } else { return Stricmp(a->GetText(0), b->GetText(0)); } } return 0; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool ReadVsProjFile(LString File, LString &Ver, LString::Array &Configs) { const char *Ext = LGetExtension(File); if (!Ext || _stricmp(Ext, "vcxproj")) return false; LFile f; if (!f.Open(File, O_READ)) return false; LXmlTree Io; LXmlTag r; if (Io.Read(&r, &f) && r.IsTag("Project")) { Ver = r.GetAttr("ToolsVersion"); LXmlTag *ItemGroup = r.GetChildTag("ItemGroup"); if (ItemGroup) for (auto c: ItemGroup->Children) { if (c->IsTag("ProjectConfiguration")) { char *s = c->GetAttr("Include"); if (s) Configs.New() = s; } } } else return false; return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// BuildThread::BuildThread(IdeProject *proj, char *makefile, bool clean, BuildConfig config, bool all, int wordsize) : LThread("BuildThread") { Proj = proj; Makefile = makefile; Clean = clean; Config = config; All = all; WordSize = wordsize; Arch = DefaultArch; Compiler = DefaultCompiler; AppHnd = Proj->GetApp()->AddDispatch(); LString Cmds = proj->d->Settings.GetStr(ProjPostBuildCommands, NULL); if (ValidStr(Cmds)) PostBuild = Cmds.SplitDelimit("\r\n"); auto Ext = LGetExtension(Makefile); if (Ext && !_stricmp(Ext, "py")) Compiler = PythonScript; else if (Ext && !_stricmp(Ext, "sln")) Compiler = VisualStudio; else if (Ext && !Stricmp(Ext, "xcodeproj")) Compiler = Xcode; else { auto Comp = Proj->GetSettings()->GetStr(ProjCompiler); if (Comp) { // Use the specified compiler... if (!stricmp(Comp, "VisualStudio")) Compiler = VisualStudio; else if (!stricmp(Comp, "MingW")) Compiler = MingW; else if (!stricmp(Comp, "gcc")) Compiler = Gcc; else if (!stricmp(Comp, "cross")) Compiler = CrossCompiler; else if (!stricmp(Comp, "IAR")) Compiler = IAR; else if (!stricmp(Comp, "Cygwin")) Compiler = Cygwin; else LAssert(!"Unknown compiler."); } } if (Compiler == DefaultCompiler) { // Use default compiler for platform... #ifdef WIN32 Compiler = VisualStudio; #else Compiler = Gcc; #endif } Run(); } BuildThread::~BuildThread() { if (SubProc) { bool b = SubProc->Interrupt(); LgiTrace("%s:%i - Sub process interrupt = %i.\n", _FL, b); } else LgiTrace("%s:%i - No sub process to interrupt.\n", _FL); uint64 Start = LCurrentTime(); bool Killed = false; while (!IsExited()) { LSleep(10); if (LCurrentTime() - Start > STOP_BUILD_TIMEOUT && SubProc) { if (Killed) { // Thread is stuck as well... ok kill that too!!! Argh - kill all the things!!!! Terminate(); LgiTrace("%s:%i - Thread killed.\n", _FL); Proj->GetApp()->PostEvent(M_BUILD_DONE); break; } else { // Kill the sub-process... bool b = SubProc->Kill(); Killed = true; LgiTrace("%s:%i - Sub process killed.\n", _FL, b); Start = LCurrentTime(); } } } } ssize_t BuildThread::Write(const void *Buffer, ssize_t Size, int Flags) { if (Proj->GetApp()) { Proj->GetApp()->PostEvent(M_APPEND_TEXT, (LMessage::Param)NewStr((char*)Buffer, Size), AppWnd::BuildTab); } return Size; } #pragma comment(lib, "version.lib") struct ProjInfo { LString Guid, Name, File; LHashTbl,ssize_t> Configs; }; LString BuildThread::FindExe() { LString::Array p = LGetPath(); if (Compiler == PythonScript) { #if defined(WINDOWS) uint32_t BestVer = 0; #else LString BestVer; #endif static LString Best; if (!Best) { const char *binName[] = {"python3", "python"}; for (int i=0; idwProductVersionMS > BestVer) { BestVer = v->dwProductVersionMS; Best = Path; } } } else if (!Best) { Best = Path; } free(Buf); #else LSubProcess p(Path, "--version"); LStringPipe o; if (p.Start()) p.Communicate(&o); auto Out = o.NewLStr(); auto Ver = Out.SplitDelimit().Last(); printf("Ver=%s\n", Ver.Get()); if (!BestVer || Stricmp(Ver.Get(), BestVer.Get()) > 0) { Best = Path; BestVer = Ver; } break; #endif } } } } return Best; } else if (Compiler == VisualStudio) { // Find the version we need: double fVer = 0.0; ProjInfo *First = NULL; LHashTbl, ProjInfo*> Projects; const char *Ext = LGetExtension(Makefile); if (Ext && !_stricmp(Ext, "sln")) { LFile f; if (f.Open(Makefile, O_READ)) { LString ProjKey = "Project("; LString StartSection = "GlobalSection("; LString EndSection = "EndGlobalSection"; LString Section; ssize_t Pos; LString::Array Ln = f.Read().SplitDelimit("\r\n"); for (size_t i = 0; i < Ln.Length(); i++) { LString s = Ln[i].Strip(); if ((Pos = s.Find(ProjKey)) >= 0) { LString::Array p = s.SplitDelimit("(),="); if (p.Length() > 5) { ProjInfo *i = new ProjInfo; i->Name = p[3].Strip(" \t\""); i->File = p[4].Strip(" \t\'\""); i->Guid = p[5].Strip(" \t\""); if (LIsRelativePath(i->File)) { char f[MAX_PATH_LEN]; LMakePath(f, sizeof(f), Makefile, ".."); LMakePath(f, sizeof(f), f, i->File); if (LFileExists(f)) i->File = f; /* else LAssert(0); */ } if (!First) First = i; Projects.Add(i->Guid, i); } } else if (s.Find(StartSection) >= 0) { auto p = s.SplitDelimit("() \t"); Section = p[1]; } else if (s.Find(EndSection) >= 0) { Section.Empty(); } else if (Section == "ProjectConfigurationPlatforms") { auto p = s.SplitDelimit(". \t"); auto i = Projects.Find(p[0]); if (i) { if (!i->Configs.Find(p[1])) { auto Idx = i->Configs.Length() + 1; i->Configs.Add(p[1], Idx); } } } else if (Section == "SolutionConfigurationPlatforms") { auto p = s.SplitDelimit(); auto config = p[0]; for (auto &it: Projects) { auto proj = it.value; if (!proj->Configs.Find(config)) proj->Configs.Add(config, proj->Configs.Length()+1); } } } } } else if (Ext && !_stricmp(Ext, "vcxproj")) { // ProjFile = Makefile; } else { if (Arch == DefaultArch) { if (sizeof(size_t) == 4) Arch = ArchX32; else Arch = ArchX64; } #ifdef _MSC_VER // Nmake file.. LString NmakePath; switch (_MSC_VER) { #ifdef _MSC_VER_VS2013 case _MSC_VER_VS2013: { if (Arch == ArchX32) NmakePath = "c:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\bin\\nmake.exe"; else NmakePath = "c:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\bin\\amd64\\nmake.exe"; break; } #endif #ifdef _MSC_VER_VS2015 case _MSC_VER_VS2015: { if (Arch == ArchX32) NmakePath = "c:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\bin\\nmake.exe"; else NmakePath = "c:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\bin\\amd64\\nmake.exe"; break; } #endif default: { LAssert(!"Impl me."); break; } } if (LFileExists(NmakePath)) { Compiler = Nmake; return NmakePath; } #endif } /* if (ProjFile && LFileExists(ProjFile)) { LString sVer; if (ReadVsProjFile(ProjFile, sVer, BuildConfigs)) { fVer = sVer.Float(); } } */ if (First) { for (auto i: First->Configs) { BuildConfigs[i.value - 1] = i.key; LFile f(First->File, O_READ); LXmlTree t; LXmlTag r; if (t.Read(&r, &f)) { if (r.IsTag("Project")) { auto ToolsVersion = r.GetAttr("ToolsVersion"); if (ToolsVersion) { fVer = atof(ToolsVersion); } } } } } if (fVer > 0.0) { for (int i=0; i LatestVer) { LatestVer = p[2].Float(); Latest = n; } } } if (Latest && LMakePath(p, sizeof(p), p, Latest) && LMakePath(p, sizeof(p), p, "common\\bin\\IarBuild.exe")) { if (LFileExists(p)) return p; } } else if (Compiler == Xcode) { return "/usr/bin/xcodebuild"; } else if (Compiler == Cygwin) { #ifdef WINDOWS LRegKey k(false, "HKEY_CURRENT_USER\\Software\\Cygwin\\Installations"); List n; k.GetValueNames(n); LString s; for (auto i:n) { s = k.GetStr(i); if (s.Find("\\??\\") == 0) s = s(4,-1); if (LDirExists(s)) { CygwinPath = s; break; } } n.DeleteArrays(); LFile::Path p(s, "bin\\make.exe"); if (p.Exists()) return p.GetFull(); #endif } else { if (Compiler == MingW) { // Have a look in the default spot first... const char *Def = "C:\\MinGW\\msys\\1.0\\bin\\make.exe"; if (LFileExists(Def)) { return Def; } } for (int i=0; iGetBasePath(); LString InitDir = InitFolder.Get(); LVariant Jobs; if (!Proj->GetApp()->GetOptions()->GetValue(OPT_Jobs, Jobs) || Jobs.CastInt32() < 1) Jobs = 2; LString TmpArgs, Include, Lib, LibPath, Path; if (Compiler == VisualStudio) { // TmpArgs.Printf("\"%s\" /make \"All - Win32 Debug\"", Makefile.Get()); LString BuildConf = "All - Win32 Debug"; if (BuildConfigs.Length()) { auto Key = toString(Config); for (size_t i=0; i= 0) { if (!BuildConf || (c.Find("x64") >= 0 && BuildConf.Find("x64") < 0)) BuildConf = c; } } } TmpArgs.Printf("\"%s\" %s \"%s\"", Makefile.Get(), Clean ? "/Clean" : "/Build", BuildConf.Get()); } else if (Compiler == Nmake) { const char *DefInc[] = { "C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\INCLUDE", "C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\ATLMFC\\INCLUDE", "C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\shared", "C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\um", "C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\winrt" }; LString f; #define ADD_PATHS(out, in) \ for (unsigned i=0; iGetChildTag("name") : NULL; if (c) Conf = c->GetContent(); } } TmpArgs.Printf("\"%s\" %s %s -log warnings", Makefile.Get(), Clean ? "-clean" : "-make", Conf.Get()); } else if (Compiler == Xcode) { LString::Array Configs; Configs.SetFixedLength(false); LString a; a.Printf("-list -project \"%s\"", Makefile.Get()); LSubProcess Ls(Exe, a); if (Ls.Start()) { LStringPipe o; Ls.Communicate(&o); auto key = "Build Configurations:"; auto lines = o.NewLStr().SplitDelimit("\n"); int inKey = -1; for (auto l: lines) { int ws = 0; for (int i=0; i=0) inKey = ws; else if (inKey>=0) { if (ws>inKey) { Configs.New() = l.Strip(); } else { inKey = -1; } } // printf("%s\n", l.Get()); } } auto Config = Configs.Length() > 0 ? Configs[0] : LString("Debug"); TmpArgs.Printf("-project \"%s\" -configuration %s", Makefile.Get(), Config.Get()); } else { if (Compiler == Cygwin) { LFile::Path p(CygwinPath, "bin"); Path = p.GetFull(); } if (Compiler == MingW) { LString a; char *Dir = strrchr(MakePath, DIR_CHAR); #if 1 TmpArgs.Printf("/C \"%s\"", Exe.Get()); /* As of MSYS v1.0.18 the support for multiple jobs causes make to hang: http://sourceforge.net/p/mingw/bugs/1950/ http://mingw-users.1079350.n2.nabble.com/MSYS-make-freezes-td7579038.html Apparently it'll be "fixed" in v1.0.19. We'll see. >:-( if (Jobs.CastInt32() > 1) a.Print(" -j %i", Jobs.CastInt32()); */ a.Printf(" -f \"%s\"", Dir ? Dir + 1 : MakePath.Get()); TmpArgs += a; #else TmpArgs.Printf("/C set"); #endif Exe = "C:\\Windows\\System32\\cmd.exe"; } else { if (Jobs.CastInt32()) TmpArgs.Printf("-j %i -f \"%s\"", Jobs.CastInt32(), MakePath.Get()); else TmpArgs.Printf("-f \"%s\"", MakePath.Get()); } if (Clean) { if (All) TmpArgs += " cleanall"; else TmpArgs += " clean"; } if (Config == BuildRelease) TmpArgs += " Build=Release"; } PostThreadEvent(AppHnd, M_SELECT_TAB, AppWnd::BuildTab); LString Msg; Msg.Printf("Making: %s\n", MakePath.Get()); // Msg.Printf("InitDir: %s\n", InitDir.Get()); Proj->GetApp()->PostEvent(M_APPEND_TEXT, (LMessage::Param)NewStr(Msg), AppWnd::BuildTab); LgiTrace("%s %s\n", Exe.Get(), TmpArgs.Get()); if (SubProc.Reset(new LSubProcess(Exe, TmpArgs))) { SubProc->SetNewGroup(false); SubProc->SetInitFolder(InitDir); if (Include) SubProc->SetEnvironment("INCLUDE", Include); if (Lib) SubProc->SetEnvironment("LIB", Lib); if (LibPath) SubProc->SetEnvironment("LIBPATHS", LibPath); if (Path) { LString Cur = getenv("PATH"); LString New = Cur + LGI_PATH_SEPARATOR + Path; SubProc->SetEnvironment("PATH", New); } // SubProc->SetEnvironment("DLL", "1"); if (Compiler == MingW) SubProc->SetEnvironment("PATH", "c:\\MingW\\bin;C:\\MinGW\\msys\\1.0\\bin;%PATH%"); if ((Status = SubProc->Start(true, false))) { // Read all the output char Buf[256]; ssize_t rd; while ( (rd = SubProc->Read(Buf, sizeof(Buf))) > 0 ) { Write(Buf, rd); } uint32_t ex = SubProc->Wait(); Print("Make exited with %i (0x%x)\n", ex, ex); if (Compiler == IAR && ex == 0 && PostBuild.Length()) { for (auto Cmd : PostBuild) { auto p = Cmd.Split(" ", 1); if (p[0].Equals("cd")) { if (p.Length() > 1) FileDev->SetCurrentFolder(p[1]); else LAssert(!"No folder for cd?"); } else { LSubProcess PostCmd(p[0], p.Length() > 1 ? p[1] : LString()); if (PostCmd.Start(true, false)) { char Buf[256]; ssize_t rd; while ( (rd = PostCmd.Read(Buf, sizeof(Buf))) > 0 ) { Write(Buf, rd); } } } } } } else { // Create a nice error message. LString ErrStr = LErrorCodeToString(SubProc->GetErrorCode()); if (ErrStr) { char *e = ErrStr.Get() + ErrStr.Length(); while (e > ErrStr && strchr(" \t\r\n.", e[-1])) *(--e) = 0; } sprintf_s(ErrBuf, sizeof(ErrBuf), "Running make failed with %i (%s)\n", SubProc->GetErrorCode(), ErrStr.Get()); Err = ErrBuf; } } } else { Err = "Couldn't find program to build makefile."; LgiTrace("%s,%i - %s.\n", _FL, Err); } AppWnd *w = Proj->GetApp(); if (w) { w->PostEvent(M_BUILD_DONE); if (Err) Proj->GetApp()->PostEvent(M_BUILD_ERR, 0, (LMessage::Param)NewStr(Err)); } else LAssert(0); return 0; } ////////////////////////////////////////////////////////////////////////////////// IdeProject::IdeProject(AppWnd *App) : IdeCommon(NULL) { Project = this; d = new IdeProjectPrivate(App, this); Tag = NewStr("Project"); } IdeProject::~IdeProject() { d->App->OnProjectDestroy(this); LXmlTag::Empty(true); DeleteObj(d); } bool IdeProject::OnNode(const char *Path, ProjectNode *Node, bool Add) { if (!Path || !Node) { LAssert(0); return false; } char Full[MAX_PATH_LEN]; if (LIsRelativePath(Path)) { LAutoString Base = GetBasePath(); if (LMakePath(Full, sizeof(Full), Base, Path)) { Path = Full; } } bool Status = false; if (Add) Status = d->Nodes.Add(Path, Node); else Status = d->Nodes.Delete(Path); LString p = Path; if (Status && CheckExists(p)) d->App->OnNode(p, Node, Add ? FindSymbolSystem::FileAdd : FindSymbolSystem::FileRemove); return Status; } void IdeProject::ShowFileProperties(const char *File) { ProjectNode *Node = NULL; // char *fp = FindFullPath(File, &Node); if (Node) { Node->OnProperties(); } } const char *IdeProject::GetFileComment() { return d->Settings.GetStr(ProjCommentFile); } const char *IdeProject::GetFunctionComment() { return d->Settings.GetStr(ProjCommentFunction); } IdeProject *IdeProject::GetParentProject() { return d->ParentProject; } void IdeProject::SetParentProject(IdeProject *p) { d->ParentProject = p; } bool IdeProject::GetChildProjects(List &c) { CollectAllSubProjects(c); return c.Length() > 0; } bool IdeProject::RelativePath(LString &Out, const char *In, bool Debug) { if (!In) return false; auto Base = GetBasePath(); if (!Base) return false; CheckExists(Base); if (Debug) LgiTrace("XmlBase='%s'\n In='%s'\n", Base.Get(), In); LToken b(Base, DIR_STR); LToken i(In, DIR_STR); char out[MAX_PATH_LEN] = ""; int outCh = 0; if (Debug) LgiTrace("Len %i-%i\n", b.Length(), i.Length()); auto ILen = i.Length() + (LDirExists(In) ? 0 : 1); auto Max = MIN(b.Length(), ILen); int Common = 0; for (; Common < Max; Common++) { #ifdef WIN32 #define StrCompare stricmp #else #define StrCompare strcmp #endif if (Debug) LgiTrace("Cmd '%s'-'%s'\n", b[Common], i[Common]); if (StrCompare(b[Common], i[Common]) != 0) { break; } } if (Debug) LgiTrace("Common=%i\n", Common); if (Common > 0) { if (Common < b.Length()) { out[outCh = 0] = 0; auto Back = b.Length() - Common; if (Debug) LgiTrace("Back=%i\n", (int)Back); for (int n=0; nSettings.GetStr(ProjExe); LString Exe; if (!PExe) { // Use the default exe name? Exe = GetExecutable(GetCurrentPlatform()); if (Exe) { printf("Exe='%s'\n", Exe.Get()); PExe = Exe; } } if (!PExe) return false; if (LIsRelativePath(PExe)) { auto Base = GetBasePath(); if (Base) LMakePath(Path, Len, Base, PExe); else return false; } else { strcpy_s(Path, Len, PExe); } return true; } LString IdeProject::GetMakefile(IdePlatform Platform) { const char *PMakefile = d->Settings.GetStr(ProjMakefile, NULL, Platform); if (!PMakefile) return LString(); LString Path; if (LIsRelativePath(PMakefile)) { auto Base = GetBasePath(); if (Base) { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), Base, PMakefile); Path = p; } } else { Path = PMakefile; } return Path; } void IdeProject::Clean(bool All, BuildConfig Config) { if (!d->Thread && d->Settings.GetStr(ProjMakefile)) { auto m = GetMakefile(PlatformCurrent); if (m) { CheckExists(m); d->Thread.Reset(new BuildThread(this, m, true, Config, All, sizeof(ssize_t)*8)); } } } char *QuoteStr(char *s) { LStringPipe p(256); while (s && *s) { if (*s == ' ') { p.Push("\\ ", 2); } else p.Push(s, 1); s++; } return p.NewStr(); } class ExecuteThread : public LThread, public LStream, public LCancel { IdeProject *Proj; LString Exe, Args, Path; int Len; ExeAction Act; int AppHnd; public: ExecuteThread(IdeProject *proj, const char *exe, const char *args, char *path, ExeAction act) : LThread("ExecuteThread") { Len = 32 << 10; Proj = proj; Act = act; Exe = exe; Args = args; Path = path; DeleteOnExit = true; AppHnd = proj->GetApp()->AddDispatch(); Run(); } ~ExecuteThread() { Cancel(); while (!IsExited()) LSleep(1); } int Main() override { PostThreadEvent(AppHnd, M_SELECT_TAB, AppWnd::OutputTab); PostThreadEvent(AppHnd, M_APPEND_TEXT, 0, AppWnd::OutputTab); if (Exe) { if (Act == ExeDebug) { LSubProcess sub("kdbg", Exe); if (Path) sub.SetInitFolder(Path); if (sub.Start()) sub.Communicate(this, NULL, this); } else if (Act == ExeValgrind) { #ifdef LINUX LString ExePath = Proj->GetExecutable(GetCurrentPlatform()); if (ExePath) { char Path[MAX_PATH_LEN]; char *ExeLeaf = LGetLeaf(Exe); strcpy_s(Path, sizeof(Path), ExeLeaf ? ExeLeaf : Exe.Get()); LTrimDir(Path); const char *Term = NULL; const char *WorkDir = NULL; const char *Execute = NULL; switch (LGetWindowManager()) { case WM_Kde: Term = "konsole"; WorkDir = "--workdir "; Execute = "-e"; break; case WM_Gnome: Term = "gnome-terminal"; WorkDir = "--working-directory="; Execute = "-x"; break; } if (Term && WorkDir && Execute) { char *e = QuoteStr(ExePath); char *p = QuoteStr(Path); const char *a = Proj->GetExeArgs() ? Proj->GetExeArgs() : ""; char Args[512]; sprintf(Args, "%s%s " "--noclose " "%s valgrind --tool=memcheck --num-callers=20 %s %s", WorkDir, p, Execute, e, a); LExecute(Term, Args); } } #endif } else { LSubProcess sub(Exe, Args); if (Path) sub.SetInitFolder(Path); if (sub.Start()) sub.Communicate(this, NULL, this); } } return 0; } ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) override { if (Len <= 0) return 0; PostThreadEvent(AppHnd, M_APPEND_TEXT, (LMessage::Param)NewStr((char*)Buffer, Size), AppWnd::OutputTab); Len -= Size; return Size; } }; LDebugContext *IdeProject::Execute(ExeAction Act, LString *ErrMsg) { auto Base = GetBasePath(); if (!Base) { if (ErrMsg) *ErrMsg = "No base path for project."; return NULL; } char e[MAX_PATH_LEN]; if (!GetExePath(e, sizeof(e))) { if (ErrMsg) *ErrMsg = "GetExePath failed."; return NULL; } if (!LFileExists(e)) { if (ErrMsg) ErrMsg->Printf("Executable '%s' doesn't exist.\n", e); return NULL; } const char *Args = d->Settings.GetStr(ProjArgs); const char *Env = d->Settings.GetStr(ProjEnv); LString InitDir = d->Settings.GetStr(ProjInitDir); int RunAsAdmin = d->Settings.GetInt(ProjDebugAdmin); #ifndef HAIKU if (Act == ExeDebug) { if (InitDir && LIsRelativePath(InitDir)) { LFile::Path p(Base); p += InitDir; InitDir = p.GetFull(); } return new LDebugContext(d->App, this, e, Args, RunAsAdmin != 0, Env, InitDir); } #endif new ExecuteThread( this, e, Args, Base, #if defined(HAIKU) || defined(WINDOWS) ExeRun // No gdb or valgrind #else Act #endif ); return NULL; } bool IdeProject::IsMakefileAScript() { auto m = GetMakefile(PlatformCurrent); if (m) { auto Ext = LGetExtension(m); if (!Stricmp(Ext, "py")) { // Not a makefile but a build script... can't update. return true; } } return false; } bool IdeProject::IsMakefileUpToDate() { if (IsMakefileAScript()) return true; // We can't know if it's up to date... List Proj; GetChildProjects(Proj); Proj.Insert(this); for (auto p: Proj) { // Is the project file modified after the makefile? auto Proj = p->GetFullPath(); uint64 ProjModTime = 0, MakeModTime = 0; LDirectory dir; if (dir.First(Proj)) { ProjModTime = dir.GetLastWriteTime(); dir.Close(); } auto m = p->GetMakefile(PlatformCurrent); if (!m) { d->App->GetBuildLog()->Print("Error: no makefile? (%s:%i)\n", _FL); break; } auto Ext = LGetExtension(m); if (!Stricmp(Ext, "py")) { // Not a makefile but a build script... can't update. return true; } if (dir.First(m)) { MakeModTime = dir.GetLastWriteTime(); dir.Close(); } // printf("Proj=%s - Timestamps " LGI_PrintfInt64 " - " LGI_PrintfInt64 "\n", Proj.Get(), ProjModTime, MakeModTime); if (ProjModTime != 0 && MakeModTime != 0 && ProjModTime > MakeModTime) { // Need to rebuild the makefile... return false; } } return true; } bool IdeProject::FindDuplicateSymbols() { LStream *Log = d->App->GetBuildLog(); Log->Print("FindDuplicateSymbols starting...\n"); List Proj; CollectAllSubProjects(Proj); Proj.Insert(this); int Lines = 0; LHashTbl,int64> Map(200000); int Found = 0; for (auto p: Proj) { LString s = p->GetExecutable(GetCurrentPlatform()); if (s) { LString Args; Args.Printf("--print-size --defined-only -C %s", s.Get()); LSubProcess Nm("nm", Args); if (Nm.Start(true, false)) { char Buf[256]; LStringPipe q; for (ssize_t Rd = 0; (Rd = Nm.Read(Buf, sizeof(Buf))); ) q.Write(Buf, Rd); LString::Array a = q.NewLStr().SplitDelimit("\r\n"); LHashTbl,bool> Local(200000); for (auto &Ln: a) { LString::Array p = Ln.SplitDelimit(" \t", 3); if (!Local.Find(p.Last())) { Local.Add(p.Last(), true); // const char *Sz = p[1]; int64 Ours = p[1].Int(16); int64 Theirs = Map.Find(p.Last()); if (Theirs >= 0) { if (Ours != Theirs) { if (Found++ < 100) Log->Print(" %s (" LPrintfInt64 " -> " LPrintfInt64 ")\n", p.Last().Get(), Ours, Theirs); } } else if (Ours >= 0) { Map.Add(p.Last(), Ours); } else { printf("Bad line: %s\n", Ln.Get()); } } Lines++; } } } else printf("%s:%i - GetExecutable failed.\n", _FL); } /* char *Sym; for (int Count = Map.First(&Sym); Count; Count = Map.Next(&Sym)) { if (Count > 1) Log->Print(" %i: %s\n", Count, Sym); } */ Log->Print("FindDuplicateSymbols finished (%i lines)\n", Lines); return false; } bool IdeProject::FixMissingFiles() { FixMissingFilesDlg(this); return true; } void IdeProject::Build(bool All, BuildConfig Config) { if (d->Thread) { d->App->GetBuildLog()->Print("Error: Already building (%s:%i)\n", _FL); return; } auto m = GetMakefile(PlatformCurrent); CheckExists(m); if (!m) { d->App->GetBuildLog()->Print("Error: no makefile? (%s:%i)\n", _FL); return; } if (GetApp()) GetApp()->PostEvent(M_APPEND_TEXT, 0, 0); SetClean([this, m, All, Config](bool ok) { if (!ok) return; if (!IsMakefileUpToDate()) CreateMakefile(GetCurrentPlatform(), true); else // Start the build thread... d->Thread.Reset ( new BuildThread ( this, m, false, Config, All, sizeof(size_t)*8 ) ); }); } void IdeProject::StopBuild() { d->Thread.Reset(); } bool IdeProject::Serialize(bool Write) { return true; } AppWnd *IdeProject::GetApp() { return d->App; } const char *IdeProject::GetIncludePaths() { return d->Settings.GetStr(ProjIncludePaths); } const char *IdeProject::GetPreDefinedValues() { return d->Settings.GetStr(ProjDefines); } const char *IdeProject::GetExeArgs() { return d->Settings.GetStr(ProjArgs); } LString IdeProject::GetExecutable(IdePlatform Platform) { LString Bin = d->Settings.GetStr(ProjExe, NULL, Platform); auto TargetType = d->Settings.GetStr(ProjTargetType, NULL, Platform); auto Base = GetBasePath(); if (Bin) { if (LIsRelativePath(Bin) && Base) { char p[MAX_PATH_LEN]; if (LMakePath(p, sizeof(p), Base, Bin)) Bin = p; } return Bin; } // Create binary name from target: auto Target = GetTargetName(Platform); if (Target) { bool IsLibrary = Stristr(TargetType, "library"); int BuildMode = d->App->GetBuildMode(); const char *Name = BuildMode ? "Release" : "Debug"; const char *Postfix = BuildMode ? "" : "d"; switch (Platform) { case PlatformWin: { if (IsLibrary) Bin.Printf("%s%s.dll", Target.Get(), Postfix); else Bin = Target; break; } case PlatformMac: { if (IsLibrary) Bin.Printf("lib%s%s.dylib", Target.Get(), Postfix); else Bin = Target; break; } case PlatformLinux: case PlatformHaiku: { if (IsLibrary) Bin.Printf("lib%s%s.so", Target.Get(), Postfix); else Bin = Target; break; } default: { LAssert(0); printf("%s:%i - Unknown platform.\n", _FL); return LString(); } } // Find the actual file... if (!Base) { printf("%s:%i - GetBasePath failed.\n", _FL); return LString(); } char Path[MAX_PATH_LEN]; LMakePath(Path, sizeof(Path), Base, Name); LMakePath(Path, sizeof(Path), Path, Bin); if (LFileExists(Path)) Bin = Path; else printf("%s:%i - '%s' doesn't exist.\n", _FL, Path); return Bin; } return LString(); } const char *IdeProject::GetFileName() { return d->FileName; } int IdeProject::GetPlatforms() { return PLATFORM_ALL; } LAutoString IdeProject::GetFullPath() { LAutoString Status; if (!d->FileName) { // LAssert(!"No path."); return Status; } LArray sections; IdeProject *proj = this; while ( proj && proj->GetFileName() && LIsRelativePath(proj->GetFileName())) { sections.AddAt(0, proj->GetFileName()); proj = proj->GetParentProject(); } if (!proj) { // LAssert(!"All projects have a relative path?"); return Status; // No absolute path in the parent projects? } char p[MAX_PATH_LEN]; strcpy_s(p, sizeof(p), proj->GetFileName()); // Copy the base path if (sections.Length() > 0) LTrimDir(p); // Trim off the filename for (int i=0; iFileName.Empty(); d->UserFile.Empty(); d->App->GetTree()->Insert(this); ProjectNode *f = new ProjectNode(this); if (f) { f->SetName("Source"); f->SetType(NodeDir); InsertTag(f); } f = new ProjectNode(this); if (f) { f->SetName("Headers"); f->SetType(NodeDir); InsertTag(f); } d->Settings.Set(ProjEditorTabSize, 4); d->Settings.Set(ProjEditorIndentSize, 4); d->Settings.Set(ProjEditorUseHardTabs, true); d->Dirty = true; Expanded(true); } ProjectStatus IdeProject::OpenFile(const char *FileName) { auto Log = d->App->GetBuildLog(); LProfile Prof("IdeProject::OpenFile"); Prof.HideResultsIfBelow(1000); Empty(); Prof.Add("Init"); d->UserNodeFlags.Empty(); if (LIsRelativePath(FileName)) { char p[MAX_PATH_LEN]; getcwd(p, sizeof(p)); LMakePath(p, sizeof(p), p, FileName); d->FileName = p; } else { d->FileName = FileName; } d->UserFile = d->FileName + "." + LCurrentUserName(); if (!d->FileName) { Log->Print("%s:%i - No filename.\n", _FL); return OpenError; } Prof.Add("FileOpen"); LFile f; LString FullPath = d->FileName.Get(); if (!CheckExists(FullPath) || !f.Open(FullPath, O_READWRITE)) { Log->Print("%s:%i - Error: Can't open '%s'.\n", _FL, FullPath.Get()); return OpenError; } Prof.Add("Xml"); LXmlTree x; LXmlTag r; if (!x.Read(&r, &f)) { Log->Print("%s:%i - Error: Can't read XML: %s\n", _FL, x.GetErrorMsg()); return OpenError; } Prof.Add("Progress Setup"); #if DEBUG_OPEN_PROGRESS int64 Nodes = r.CountTags(); LProgressDlg Prog(d->App, 1000); Prog.SetDescription("Loading project..."); Prog.SetLimits(0, Nodes); Prog.SetYieldTime(1000); Prog.SetAlwaysOnTop(true); #endif Prof.Add("UserFile"); if (LFileExists(d->UserFile)) { LFile Uf; if (Uf.Open(d->UserFile, O_READ)) { LString::Array Ln = Uf.Read().SplitDelimit("\n"); for (unsigned i=0; iUserNodeFlags.Add((int)p[0].Int(), (int)p[1].Int(16)); } } } if (!r.IsTag("Project")) { Log->Print("%s:%i - No 'Project' tag.\n", _FL); return OpenError; } Prof.Add("OnOpen"); bool Ok = OnOpen( #if DEBUG_OPEN_PROGRESS &Prog, #else NULL, #endif &r); #if DEBUG_OPEN_PROGRESS if (Prog.IsCancelled()) return OpenCancel; else #endif if (!Ok) return OpenError; Prof.Add("Insert"); d->App->GetTree()->Insert(this); Expanded(true); Prof.Add("Serialize"); d->Settings.Serialize(&r, false /* read */); return OpenOk; } bool IdeProject::SaveFile() { auto Full = GetFullPath(); if (ValidStr(Full) && d->Dirty) { LFile f; if (f.Open(Full, O_WRITE)) { f.SetSize(0); LXmlTree x; d->Settings.Serialize(this, true /* write */); if (x.Write(this, &f)) d->Dirty = false; else LgiTrace("%s:%i - Failed to write XML.\n", _FL); } else LgiTrace("%s:%i - Couldn't open '%s' for writing.\n", _FL, Full.Get()); } if (d->UserFileDirty) { LFile f; LAssert(d->UserFile.Get()); if (f.Open(d->UserFile, O_WRITE)) { f.SetSize(0); // Save user file details.. // int Id; // for (int Flags = d->UserNodeFlags.First(&Id); Flags >= 0; Flags = d->UserNodeFlags.Next(&Id)) for (auto i : d->UserNodeFlags) { f.Print("%i,%x\n", i.key, i.value); } d->UserFileDirty = false; } } printf("\tIdeProject::SaveFile %i %i\n", d->Dirty, d->UserFileDirty); return !d->Dirty && !d->UserFileDirty; } void IdeProject::SetDirty() { d->Dirty = true; d->App->OnProjectChange(); } void IdeProject::SetUserFileDirty() { d->UserFileDirty = true; } bool IdeProject::GetExpanded(int Id) { int f = d->UserNodeFlags.Find(Id); return f >= 0 ? f : false; } void IdeProject::SetExpanded(int Id, bool Exp) { if (d->UserNodeFlags.Find(Id) != (int)Exp) { d->UserNodeFlags.Add(Id, Exp); SetUserFileDirty(); } } int IdeProject::AllocateId() { return d->NextNodeId++; } template bool CheckExists(LAutoString Base, T &p, Fn Setter, bool Debug) { LFile::Path Full; bool WasRel = LIsRelativePath(p); if (WasRel) { Full = Base.Get(); Full += p.Get(); } else Full = p.Get(); bool Ret = Full.Exists(); if (!Ret) { // Is the case wrong? for (int i=1; i%s\n", i, Leaf.Get(), dir.GetName()); Leaf = dir.GetName(); Matched = true; break; } } if (!Matched) break; } } if ((Ret = Full.Exists())) { LString Old = p.Get(); if (WasRel) { auto r = LMakeRelativePath(Base, Full); Setter(p, r); } else { Setter(p, Full.GetFull()); } if (Debug) printf("%s -> %s\n", Old.Get(), p.Get()); } } if (Debug) printf("CheckExists '%s' = %i\n", Full.GetFull().Get(), Ret); return Ret; } bool IdeProject::CheckExists(LString &p, bool Debug) { return ::CheckExists(GetBasePath(), p, [](LString &o, const char *i) { o = i; }, Debug); } bool IdeProject::CheckExists(LAutoString &p, bool Debug) { return ::CheckExists(GetBasePath(), p, [](LAutoString &o, const char *i) { o.Reset(NewStr(i)); }, Debug); } bool IdeProject::GetClean() { for (auto i: *this) { ProjectNode *p = dynamic_cast(i); if (p && !p->GetClean()) return false; } return !d->Dirty && !d->UserFileDirty; } void IdeProject::SetClean(std::function OnDone) { auto CleanNodes = [this, OnDone]() { // printf("IdeProject.SetClean.CleanNodes\n"); for (auto i: *this) { ProjectNode *p = dynamic_cast(i); if (!p) break; p->SetClean(); } if (OnDone) OnDone(true); }; // printf("IdeProject.SetClean dirty=%i,%i validfile=%i\n", d->Dirty, d->UserFileDirty, ValidStr(d->FileName)); if (d->Dirty || d->UserFileDirty) { if (ValidStr(d->FileName)) SaveFile(); else { auto s = new LFileSelect; s->Parent(Tree); s->Name("Project.xml"); s->Save([this, OnDone, CleanNodes](auto s, auto ok) { // printf("IdeProject.SetClean.FileSelect ok=%i\n", ok); if (ok) { d->FileName = s->Name(); d->UserFile = d->FileName + "." + LCurrentUserName(); d->App->OnFile(d->FileName, true); Update(); CleanNodes(); } else { if (OnDone) OnDone(false); } delete s; }); return; } } CleanNodes(); } const char *IdeProject::GetText(int Col) { if (d->FileName) { char *s = strrchr(d->FileName, DIR_CHAR); return s ? s + 1 : d->FileName.Get(); } return Untitled; } int IdeProject::GetImage(int Flags) { return 0; } void IdeProject::Empty() { LXmlTag *t; while (Children.Length() > 0 && (t = Children[0])) { ProjectNode *n = dynamic_cast(t); if (n) { n->Remove(); } DeleteObj(t); } } LXmlTag *IdeProject::Create(char *Tag) { if (!stricmp(Tag, TagSettings)) return NULL; return new ProjectNode(this); } void IdeProject::OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { LSubMenu Sub; Sub.AppendItem("New Folder", IDM_NEW_FOLDER); Sub.AppendItem("New Web Folder", IDM_WEB_FOLDER); Sub.AppendSeparator(); Sub.AppendItem("Build", IDM_BUILD); Sub.AppendItem("Clean Project", IDM_CLEAN_PROJECT); Sub.AppendItem("Clean All", IDM_CLEAN_ALL); Sub.AppendItem("Rebuild Project", IDM_REBUILD_PROJECT); Sub.AppendItem("Rebuild All", IDM_REBUILD_ALL); Sub.AppendSeparator(); Sub.AppendItem("Sort Children", IDM_SORT_CHILDREN); Sub.AppendSeparator(); Sub.AppendItem("Settings", IDM_SETTINGS, true); Sub.AppendItem("Insert Dependency", IDM_INSERT_DEP); m.ToScreen(); auto c = _ScrollPos(); m.x -= c.x; m.y -= c.y; switch (Sub.Float(Tree, m.x, m.y)) { case IDM_NEW_FOLDER: { auto Name = new LInput(Tree, "", "Name:", AppName); Name->DoModal([this, Name](auto d, auto code) { if (code) GetSubFolder(this, Name->GetStr(), true); delete d; }); break; } case IDM_WEB_FOLDER: { WebFldDlg *Dlg = new WebFldDlg(Tree, 0, 0, 0); Dlg->DoModal([Dlg, this](auto d, auto code) { if (Dlg->Ftp && Dlg->Www) { IdeCommon *f = GetSubFolder(this, Dlg->Name, true); if (f) { f->SetAttr(OPT_Ftp, Dlg->Ftp); f->SetAttr(OPT_Www, Dlg->Www); } } delete Dlg; }); break; } case IDM_BUILD: { StopBuild(); Build(true, d->App->GetBuildMode()); break; } case IDM_CLEAN_PROJECT: { Clean(false, d->App->GetBuildMode()); break; } case IDM_CLEAN_ALL: { Clean(true, d->App->GetBuildMode()); break; } case IDM_REBUILD_PROJECT: { StopBuild(); Clean(false, d->App->GetBuildMode()); Build(false, d->App->GetBuildMode()); break; } case IDM_REBUILD_ALL: { StopBuild(); Clean(true, d->App->GetBuildMode()); Build(true, d->App->GetBuildMode()); break; } case IDM_SORT_CHILDREN: { SortChildren(); Project->SetDirty(); break; } case IDM_SETTINGS: { d->Settings.Edit(Tree, [this]() { SetDirty(); }); break; } case IDM_INSERT_DEP: { LFileSelect *s = new LFileSelect; s->Parent(Tree); s->Type("Project", "*.xml"); s->Open([this](auto s, bool ok) { if (ok) { ProjectNode *New = new ProjectNode(this); if (New) { New->SetFileName(s->Name()); New->SetType(NodeDependancy); InsertTag(New); SetDirty(); } } delete s; }); break; } } } } char *IdeProject::FindFullPath(const char *File, ProjectNode **Node) { char *Full = 0; for (auto i:*this) { ProjectNode *c = dynamic_cast(i); if (!c) break; ProjectNode *n = c->FindFile(File, &Full); if (n) { if (Node) *Node = n; break; } } return Full; } bool IdeProject::HasNode(ProjectNode *Node) { for (auto i:*this) { ProjectNode *c = dynamic_cast(i); if (!c) break; if (c->HasNode(Node)) return true; } return false; } bool IdeProject::GetAllNodes(LArray &Nodes) { for (auto i:*this) { ProjectNode *c = dynamic_cast(i); if (!c) break; c->AddNodes(Nodes); } return true; } bool IdeProject::InProject(bool FuzzyMatch, const char *Path, bool Open, IdeDoc **Doc) { if (!Path) return false; // Search complete path first... ProjectNode *n = d->Nodes.Find(Path); if (!n && FuzzyMatch) { // No match, do partial matching. const char *Leaf = LGetLeaf(Path); auto PathLen = strlen(Path); auto LeafLen = strlen(Leaf); uint32_t MatchingScore = 0; // Traverse all nodes and try and find the best fit. // const char *p; // for (ProjectNode *Cur = d->Nodes.First(&p); Cur; Cur = d->Nodes.Next(&p)) for (auto Cur : d->Nodes) { int CurPlatform = Cur.value->GetPlatforms(); uint32_t Score = 0; if (stristr(Cur.key, Path)) { Score += PathLen; } else if (stristr(Cur.key, Leaf)) { Score += LeafLen; } const char *pLeaf = LGetLeaf(Cur.key); if (pLeaf && !stricmp(pLeaf, Leaf)) { Score |= 0x40000000; } if (Score && (CurPlatform & PLATFORM_CURRENT) != 0) { Score |= 0x80000000; } if (Score > MatchingScore) { MatchingScore = Score; n = Cur.value; } } } if (n && Doc) { *Doc = n->Open(); } return n != 0; } char *GetQuotedStr(char *Str) { char *s = strchr(Str, '\"'); if (s) { s++; char *e = strchr(s, '\"'); if (e) { return NewStr(s, e - s); } } return 0; } void IdeProject::ImportDsp(const char *File) { if (File && LFileExists(File)) { char Base[256]; strcpy(Base, File); LTrimDir(Base); char *Dsp = LReadTextFile(File); if (Dsp) { LToken Lines(Dsp, "\r\n"); IdeCommon *Current = this; bool IsSource = false; for (int i=0; iGetSubFolder(this, Folder, true); if (Sub) { Current = Sub; } DeleteArray(Folder); } } else if (strnicmp(L, "# End Group", 11) == 0) { IdeCommon *Parent = dynamic_cast(Current->GetParent()); if (Parent) { Current = Parent; } } else if (strnicmp(L, "# Begin Source", 14) == 0) { IsSource = true; } else if (strnicmp(L, "# End Source", 12) == 0) { IsSource = false; } else if (Current && IsSource && strncmp(L, "SOURCE=", 7) == 0) { ProjectNode *New = new ProjectNode(this); if (New) { char *Src = 0; if (strchr(L, '\"')) { Src = GetQuotedStr(L); } else { Src = NewStr(L + 7); } if (Src) { // Make absolute path char Abs[256]; LMakePath(Abs, sizeof(Abs), Base, ToUnixPath(Src)); // Make relitive path New->SetFileName(Src); DeleteArray(Src); } Current->InsertTag(New); SetDirty(); } } } DeleteArray(Dsp); } } } IdeProjectSettings *IdeProject::GetSettings() { return &d->Settings; } bool IdeProject::BuildIncludePaths(LArray &Paths, bool Recurse, bool IncludeSystem, IdePlatform Platform) { List Projects; if (Recurse) GetChildProjects(Projects); Projects.Insert(this, 0); LHashTbl, bool> Map; for (auto p: Projects) { LString ProjInclude = d->Settings.GetStr(ProjIncludePaths, NULL, Platform); LAutoString Base = p->GetBasePath(); const char *Delim = ",;\r\n"; LString::Array In, Out; LString::Array a = ProjInclude.SplitDelimit(Delim); In = a; if (IncludeSystem) { LString SysInclude = d->Settings.GetStr(ProjSystemIncludes, NULL, Platform); a = SysInclude.SplitDelimit(Delim); In.SetFixedLength(false); In.Add(a); } for (unsigned i=0; i 1 ? a[1].Get() : NULL); LStringPipe Buf; if (Proc.Start()) { Proc.Communicate(&Buf); LString result = Buf.NewLStr(); a = result.Split(" \t\r\n"); for (int i=0; i Nodes; if (p->GetAllNodes(Nodes)) { auto Base = p->GetFullPath(); if (Base) { LTrimDir(Base); for (auto &n: Nodes) { if (n->GetType() == NodeHeader && // Only look at headers. (n->GetPlatforms() & (1 << Platform)) != 0) // Exclude files not on this platform. { auto f = n->GetFileName(); char p[MAX_PATH_LEN]; if (f && LMakePath(p, sizeof(p), Base, f)) { char *l = strrchr(p, DIR_CHAR); if (l) *l = 0; if (!Map.Find(p)) { Map.Add(p, true); } } } } } } } // char *p; // for (bool b = Map.First(&p); b; b = Map.Next(&p)) for (auto p : Map) Paths.Add(p.key); return true; } void IdeProjectPrivate::CollectAllFiles(LTreeNode *Base, LArray &Files, bool SubProjects, int Platform) { for (auto i:*Base) { IdeProject *Proj = dynamic_cast(i); if (Proj) { if (Proj->GetParentProject() && !SubProjects) { continue; } } else { ProjectNode *p = dynamic_cast(i); if (p) { if (p->GetType() == NodeSrc || p->GetType() == NodeHeader) { if (p->GetPlatforms() & Platform) { Files.Add(p); } } } } CollectAllFiles(i, Files, SubProjects, Platform); } } LString IdeProject::GetTargetName(IdePlatform Platform) { LString Status; const char *t = d->Settings.GetStr(ProjTargetName, NULL, Platform); if (ValidStr(t)) { // Take target name from the settings Status = t; } else { char *s = strrchr(d->FileName, DIR_CHAR); if (s) { // Generate the target executable name char Target[MAX_PATH_LEN]; strcpy_s(Target, sizeof(Target), s + 1); s = strrchr(Target, '.'); if (s) *s = 0; strlwr(Target); s = Target; for (char *i = Target; *i; i++) { if (*i != '.' && *i != ' ') { *s++ = *i; } } *s = 0; Status = Target; } } return Status; } LString IdeProject::GetTargetFile(IdePlatform Platform) { LString Target = GetTargetName(PlatformCurrent); if (!Target) { LAssert(!"No target?"); return LString(); } const char *TargetType = d->Settings.GetStr(ProjTargetType); if (!TargetType) { LAssert(!"Needs a target type."); return LString(); } if (!stricmp(TargetType, "Executable")) { return Target; } else if (!stricmp(TargetType, "DynamicLibrary")) { char t[MAX_PATH_LEN]; auto DefExt = PlatformDynamicLibraryExt(Platform); strcpy_s(t, sizeof(t), Target); auto tCh = strlen(t); char *ext = LGetExtension(t); if (!ext) snprintf(t+tCh, sizeof(t)-tCh, ".%s", DefExt); else if (stricmp(ext, DefExt)) strcpy(ext, DefExt); return t; } else if (!stricmp(TargetType, "StaticLibrary")) { LString Ret; Ret.Printf("%s.%s", Target.Get(), PlatformSharedLibraryExt(Platform)); return Ret; } return LString(); } struct ProjDependency { bool Scanned; LAutoString File; ProjDependency(const char *f) { Scanned = false; File.Reset(NewStr(f)); } }; bool IdeProject::GetAllDependencies(LArray &Files, IdePlatform Platform) { if (!GetTree()->Lock(_FL)) return false; LHashTbl, ProjDependency*> Deps; auto Base = GetBasePath(); // Build list of all the source files... LArray Src; CollectAllSource(Src, Platform); // Get all include paths LArray IncPaths; BuildIncludePaths(IncPaths, false, false, Platform); // Add all source to dependencies for (int i=0; i Unscanned; do { // Find all the unscanned dependencies Unscanned.Length(0); for (auto d : Deps) { if (!d.value->Scanned) Unscanned.Add(d.value); } for (int i=0; iScanned = true; char *Src = d->File; char Full[MAX_PATH_LEN]; if (LIsRelativePath(d->File)) { LMakePath(Full, sizeof(Full), Base, d->File); Src = Full; } LArray SrcDeps; if (GetDependencies(Src, IncPaths, SrcDeps, Platform)) { for (auto File: SrcDeps) { // Add include to dependencies... if (LIsRelativePath(File)) { LMakePath(Full, sizeof(Full), Base, File); File = Full; } if (!Deps.Find(File)) { Deps.Add(File, new ProjDependency(File)); } } SrcDeps.DeleteArrays(); } } } while (Unscanned.Length() > 0); for (auto d : Deps) { Files.Add(d.value->File.Release()); } Deps.DeleteObjects(); GetTree()->Unlock(); return true; } bool IdeProject::GetDependencies(const char *InSourceFile, LArray &IncPaths, LArray &Files, IdePlatform Platform) { LString SourceFile = InSourceFile; if (!CheckExists(SourceFile)) { LgiTrace("%s:%i - can't read '%s'\n", _FL, SourceFile.Get()); return false; } LAutoString c8(LReadTextFile(SourceFile)); if (!c8) return false; LArray Headers; if (!BuildHeaderList(c8, Headers, IncPaths, false)) return false; for (int n=0; nCreateMakefile) { if (d->CreateMakefile->IsExited()) d->CreateMakefile.Reset(); else { d->App->GetBuildLog()->Print("%s:%i - Makefile thread still running.\n", _FL); return false; } } if (Platform == PlatformCurrent) Platform = GetCurrentPlatform(); return d->CreateMakefile.Reset(new MakefileThread(d, Platform, BuildAfterwards)); } void IdeProject::OnMakefileCreated() { if (d->CreateMakefile) { d->CreateMakefile.Reset(); if (MakefileThread::Instances == 0) GetApp()->PostEvent(M_LAST_MAKEFILE_CREATED); } } //////////////////////////////////////////////////////////////////////////////////////////// IdeTree::IdeTree() : LTree(IDC_PROJECT_TREE, 0, 0, 100, 100) { Hit = 0; MultiSelect(true); } void IdeTree::OnCreate() { SetWindow(this); } void IdeTree::OnDragExit() { SelectDropTarget(0); } int IdeTree::WillAccept(LDragFormats &Formats, LPoint p, int KeyState) { static bool First = true; Formats.SupportsFileDrops(); Formats.Supports(NODE_DROP_FORMAT); First = false; if (Formats.Length() == 0) { LgiTrace("%s:%i - No valid drop formats.\n", _FL); return DROPEFFECT_NONE; } Hit = ItemAtPoint(p.x, p.y); if (!Hit) { SelectDropTarget(NULL); return DROPEFFECT_NONE; } if (Formats.HasFormat(LGI_FileDropFormat)) { SelectDropTarget(Hit); return DROPEFFECT_LINK; } IdeCommon *Src = dynamic_cast(Selection()); IdeCommon *Dst = dynamic_cast(Hit); if (Src && Dst) { // Check this folder is not a child of the src for (IdeCommon *n=Dst; n; n=dynamic_cast(n->GetParent())) { if (n == Src) return DROPEFFECT_NONE; } } // Valid target SelectDropTarget(Hit); return DROPEFFECT_MOVE; } int IdeTree::OnDrop(LArray &Data, LPoint p, int KeyState) { int Ret = DROPEFFECT_NONE; SelectDropTarget(NULL); if (!(Hit = ItemAtPoint(p.x, p.y))) return Ret; for (unsigned n=0; nType == GV_BINARY && Data->Value.Binary.Length == sizeof(ProjectNode*)) { ProjectNode *Src = ((ProjectNode**)Data->Value.Binary.Data)[0]; if (Src) { ProjectNode *Folder = dynamic_cast(Hit); while (Folder && Folder->GetType() != NodeDir) Folder = dynamic_cast(Folder->GetParent()); IdeCommon *Dst = dynamic_cast(Folder?Folder:Hit); if (Dst) { // Check this folder is not a child of the src for (IdeCommon *n=Dst; n; n=dynamic_cast(n->GetParent())) { if (n == Src) return DROPEFFECT_NONE; } // Detach LTreeItem *i = dynamic_cast(Src); i->Detach(); if (Src->LXmlTag::Parent) { LAssert(Src->LXmlTag::Parent->Children.HasItem(Src)); Src->LXmlTag::Parent->Children.Delete(Src); } // Attach Src->LXmlTag::Parent = Dst; Dst->Children.SetFixedLength(false); Dst->Children.Add(Src); Dst->Children.SetFixedLength(true); Dst->Insert(Src); // Dirty Src->GetProject()->SetDirty(); } Ret = DROPEFFECT_MOVE; } } } else if (dd.IsFileDrop()) { ProjectNode *Folder = dynamic_cast(Hit); while (Folder && Folder->GetType() > NodeDir) Folder = dynamic_cast(Folder->GetParent()); IdeCommon *Dst = dynamic_cast(Folder?Folder:Hit); if (Dst) { AddFilesProgress Prog(this); LDropFiles Df(dd); for (int i=0; iAddFiles(&Prog, Df[i])) Ret = DROPEFFECT_LINK; } } } else LgiTrace("%s:%i - Unknown drop format: %s.\n", _FL, dd.Format.Get()); } return Ret; } ///////////////////////////////////////////////////////////////////////////////////////////////// AddFilesProgress::AddFilesProgress(LViewI *par) { v = 0; Cancel = false; Msg = NULL; SetParent(par); Ts = LCurrentTime(); LRect r(0, 0, 140, 100); SetPos(r); MoveSameScreen(par); Name("Importing files..."); LString::Array a = LString(DefaultExt).SplitDelimit(","); for (unsigned i=0; iGetCell(0, 0); c->Add(new LTextLabel(-1, 0, 0, -1, -1, "Loaded:")); c = t->GetCell(1, 0); c->Add(Msg = new LTextLabel(-1, 0, 0, -1, -1, "...")); c = t->GetCell(0, 1, true, 2); c->TextAlign(LCss::Len(LCss::AlignRight)); c->Add(new LButton(IDCANCEL, 0, 0, -1, -1, "Cancel")); } int64 AddFilesProgress::Value() { return v; } void AddFilesProgress::Value(int64 val) { v = val; uint64 Now = LCurrentTime(); if (Visible()) { if (Now - Ts > 200) { if (Msg) { Msg->Value(v); Msg->SendNotify(LNotifyTableLayoutRefresh); } Ts = Now; } } else if (Now - Ts > 1000) { DoModeless(); SetAlwaysOnTop(true); } } int AddFilesProgress::OnNotify(LViewI *c, LNotification n) { if (c->GetId() == IDCANCEL) Cancel = true; return 0; } diff --git a/include/lgi/common/LgiDefs.h b/include/lgi/common/LgiDefs.h --- a/include/lgi/common/LgiDefs.h +++ b/include/lgi/common/LgiDefs.h @@ -1,581 +1,589 @@ /** \file \author Matthew Allen \date 24/9/1999 \brief Defines and types Copyright (C) 1999-2004, Matthew Allen */ #ifndef _LGIDEFS_H_ #define _LGIDEFS_H_ #ifdef HAIKU #include #endif #include "LgiInc.h" #if defined(WIN32) && defined(__GNUC__) #define PLATFORM_MINGW #endif #include // Unsafe typedefs, for backward compatibility typedef unsigned char uchar; typedef unsigned short ushort; typedef unsigned int uint; typedef unsigned long ulong; // Length safe typedesf, use these in new code #ifdef _MSC_VER /// 64-bit signed int type (size safe, guaranteed to be 64 bits) typedef signed __int64 int64; /// 64-bit unsigned int type (size safe, guaranteed to be 64 bits) typedef unsigned __int64 uint64; #ifndef _WCHAR_T_DEFINED #include #endif #pragma warning(error:4263) #ifdef _WIN64 typedef signed __int64 ssize_t; #else typedef signed int ssize_t; #endif #elif defined(LINUX) /// 64-bit signed int type (size safe, guaranteed to be 64 bits) typedef int64_t int64; /// 64-bit unsigned int type (size safe, guaranteed to be 64 bits) typedef uint64_t uint64; #elif !defined(HAIKU) /// 64-bit signed int type (size safe, guaranteed to be 64 bits) typedef signed long long int64; /// 64-bit unsigned int type (size safe, guaranteed to be 64 bits) typedef unsigned long long uint64; #endif #ifndef __cplusplus #include #if defined(_MSC_VER) && _MSC_VER<1800 typedef unsigned char bool; #define true 1 #define false 0 #else #include #endif #endif /// \brief Wide unicode char /// /// This is 16 bits on Win32 and Mac, but 32 bits on unix platforms. There are a number /// of wide character string function available for manipulating wide char strings. /// /// Firstly to convert to and from utf-8 there is: ///
    ///
  • Utf8ToWide() ///
  • WideToUtf8() ///
/// /// Wide versions of standard library functions are available: ///
    ///
  • StrchrW() ///
  • StrrchrW() ///
  • StrnchrW() ///
  • StrstrW() ///
  • StristrW() ///
  • StrnstrW() ///
  • StrnistrW() ///
  • StrcmpW() ///
  • StricmpW() ///
  • StrncmpW() ///
  • StrnicmpW() ///
  • StrcpyW() ///
  • StrncpyW() ///
  • StrlenW() ///
  • StrcatW() ///
  • HtoiW() ///
  • NewStrW() ///
  • TrimStrW() ///
  • ValidStrW() ///
  • MatchStrW() ///
#include typedef wchar_t char16; #if !WINNATIVE #ifdef UNICODE typedef char16 TCHAR; #ifndef _T #define _T(arg) L##arg #endif #else typedef char TCHAR; #ifndef _T #define _T(arg) arg #endif #endif #endif #if defined(_MSC_VER) #if _MSC_VER >= 1400 #ifdef _WIN64 typedef __int64 NativeInt; typedef unsigned __int64 UNativeInt; #else typedef _W64 int NativeInt; typedef _W64 unsigned int UNativeInt; #endif #else typedef int NativeInt; typedef unsigned int UNativeInt; #endif #else #if __LP64__ typedef int64 NativeInt; typedef uint64 UNativeInt; #else typedef int NativeInt; typedef unsigned int UNativeInt; #endif #endif /// Generic pointer to any base type. Used when addressing continuous data of /// different types. typedef union { int8_t *s8; uint8_t *u8; int16_t *s16; uint16_t *u16; int32_t *s32; uint32_t *u32; int64 *s64; uint64 *u64; NativeInt *ni; UNativeInt *uni; char *c; char16 *w; float *f; double *d; #ifdef __cplusplus bool *b; #else unsigned char *b; #endif void **vp; int i; } LPointer; // Basic macros #define limit(i,l,u) (((i)<(l)) ? (l) : (((i)>(u)) ? (u) : (i))) // #define makelong(a, b) ((a)<<16 | (b&0xFFFF)) // #define loword(a) (a&0xFFFF) // #define hiword(a) (a>>16) #undef ABS #ifdef __cplusplus template inline T ABS(T v) { if (v < 0) return -v; return v; } #else #define ABS(v) ((v) < 0 ? -(v) : (v)) #endif /// Returns true if 'c' is an ascii character #define IsAlpha(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) /// Returns true if 'c' is a digit (number) #define IsDigit(c) ((c) >= '0' && (c) <= '9') /// Returns true if 'c' is a hexadecimal digit #define IsHexDigit(c) ( \ ((c) >= '0' && (c) <= '9') || \ ((c) >= 'a' && (c) <= 'f') || \ ((c) >= 'A' && (c) <= 'F') \ ) // Byte swapping #define LgiSwap16(a) ( (((a) & 0xff00) >> 8) | \ (((a) & 0x00ff) << 8) ) #define LgiSwap32(a) ( (((a) & 0xff000000) >> 24) | \ (((a) & 0x00ff0000) >> 8) | \ (((a) & 0x0000ff00) << 8) | \ (((a) & 0x000000ff) << 24) ) #ifdef __GNUC__ #define LgiSwap64(a) ( (((a) & 0xff00000000000000LLU) >> 56) | \ (((a) & 0x00ff000000000000LLU) >> 40) | \ (((a) & 0x0000ff0000000000LLU) >> 24) | \ (((a) & 0x000000ff00000000LLU) >> 8) | \ (((a) & 0x00000000ff000000LLU) << 8) | \ (((a) & 0x0000000000ff0000LLU) << 24) | \ (((a) & 0x000000000000ff00LLU) << 40) | \ (((a) & 0x00000000000000ffLLU) << 56) ) #else #define LgiSwap64(a) ( (((a) & 0xff00000000000000) >> 56) | \ (((a) & 0x00ff000000000000) >> 40) | \ (((a) & 0x0000ff0000000000) >> 24) | \ (((a) & 0x000000ff00000000) >> 8) | \ (((a) & 0x00000000ff000000) << 8) | \ (((a) & 0x0000000000ff0000) << 24) | \ (((a) & 0x000000000000ff00) << 40) | \ (((a) & 0x00000000000000ff) << 56) ) #endif +#ifdef __cplusplus +// Define a way of creating a 4 character code string literal as an int. +constexpr uint32_t Lgi4CC( char const p[5] ) +{ + return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; +} +#endif + // Asserts LgiFunc void _lgi_assert(bool b, const char *test, const char *file, int line); #ifdef _DEBUG #define LAssert(b) _lgi_assert(b, #b, __FILE__, __LINE__) #else #define LAssert(b) ((void)0) #endif // Good ol NULLy #ifndef NULL #define NULL 0 #endif // Slashes and quotes #define IsSlash(c) (((c)=='/')||((c)=='\\')) #define IsQuote(c) (((c)=='\"')||((c)=='\'')) // Some objectish ones #define ZeroObj(obj) memset(&obj, 0, sizeof(obj)) #ifndef CountOf #define CountOf(array) (sizeof(array)/sizeof(array[0])) #endif #ifdef __cplusplus template void LSwap(T &a, T &b) { T tmp = a; a = b; b = tmp; } #endif #ifndef MEMORY_DEBUG #define DeleteObj(obj) if (obj) { delete obj; obj = 0; } #define DeleteArray(obj) if (obj) { delete [] obj; obj = 0; } #endif // Flags #define SetFlag(i, f) (i) |= (f) #define ClearFlag(i, f) (i) &= ~(f) #define TestFlag(i, f) (((i) & (f)) != 0) // Defines /// Enum of all the operating systems we might be running on. enum LgiOs { /// \brief Unknown OS /// \sa LGetOs LGI_OS_UNKNOWN = 0, /// \brief Windows 95, 98[se] or ME. (Not supported) /// \sa LGetOs LGI_OS_WIN9X, /// \brief Windows 10+ 32bit. (Not supported) /// \sa LGetOs LGI_OS_WIN32, /// \brief Windows 10+ 64bit. (Supported) /// \sa LGetOs LGI_OS_WIN64, /// \brief BeOS/Haiku. (Somewhat supported) /// \sa LGetOs LGI_OS_HAIKU, /// \brief Linux. (Kernels v2.4 and up supported) /// \sa LGetOs LGI_OS_LINUX, /// \brief Mac OS X 10.15 or later. (Supported) /// \sa LGetOs LGI_OS_MAC_OS_X, /// One higher than the maximum OS define LGI_OS_MAX, }; // Edge types enum LEdge { EdgeNone, EdgeXpSunken, EdgeXpRaised, EdgeXpChisel, EdgeXpFlat, EdgeWin7FocusSunken, EdgeWin7Sunken, }; #define DefaultSunkenEdge EdgeWin7Sunken #define DefaultRaisedEdge EdgeXpRaised // Cursors enum LCursor { /// Blank/invisible cursor LCUR_Blank, /// Normal arrow LCUR_Normal, /// Upwards arrow LCUR_UpArrow, /// Downwards arrow LCUR_DownArrow, /// Left arrow LCUR_LeftArrow, /// Right arrow LCUR_RightArrow, /// Crosshair LCUR_Cross, /// Hourglass/watch LCUR_Wait, /// Ibeam/text entry LCUR_Ibeam, /// Vertical resize (|) LCUR_SizeVer, /// Horizontal resize (-) LCUR_SizeHor, /// Diagonal resize (/) LCUR_SizeBDiag, /// Diagonal resize (\) LCUR_SizeFDiag, /// All directions resize LCUR_SizeAll, /// Vertical splitting LCUR_SplitV, /// Horziontal splitting LCUR_SplitH, /// A pointing hand LCUR_PointingHand, /// A slashed circle LCUR_Forbidden, /// Copy Drop LCUR_DropCopy, /// Copy Move LCUR_DropMove, }; // General Event Flags #define LGI_EF_LCTRL (1 << 0) #define LGI_EF_RCTRL (1 << 1) #define LGI_EF_CTRL (LGI_EF_LCTRL | LGI_EF_RCTRL) #define LGI_EF_LALT (1 << 2) #define LGI_EF_RALT (1 << 3) #define LGI_EF_ALT (LGI_EF_LALT | LGI_EF_RALT) #define LGI_EF_LSHIFT (1 << 4) #define LGI_EF_RSHIFT (1 << 5) #define LGI_EF_SHIFT (LGI_EF_LSHIFT | LGI_EF_RSHIFT) #define LGI_EF_DOWN (1 << 6) #define LGI_EF_DOUBLE (1 << 7) #define LGI_EF_CAPS_LOCK (1 << 8) #define LGI_EF_IS_CHAR (1 << 9) #define LGI_EF_IS_NOT_CHAR (1 << 10) #define LGI_EF_SYSTEM (1 << 11) // Windows key/Apple key etc // Mouse Event Flags #define LGI_EF_LEFT (1 << 16) #define LGI_EF_MIDDLE (1 << 17) #define LGI_EF_RIGHT (1 << 18) #define LGI_EF_MOVE (1 << 19) #define LGI_EF_XBTN1 (1 << 20) #define LGI_EF_XBTN2 (1 << 21) // Emit compiler warnings #define __STR2__(x) #x #define __STR1__(x) __STR2__(x) #define __LOC__ __FILE__ "(" __STR1__(__LINE__) ") : Warning: " // To use just do #pragma message(__LOC__"My warning message") // Simple definition of breakable unicode characters #define LGI_BreakableChar(c) ( \ (c) == '\n' || \ (c) == ' ' || \ (c) == '\t' || \ ( (c) >= 0x3040 && (c) <= 0x30FF ) || \ ( (c) >= 0x3300 && (c) <= 0x9FAF ) \ ) // Os metrics enum LSystemMetric { /// Get the standard window horizontal border size /// \sa LApp::GetMetric() LGI_MET_DECOR_X = 1, /// Get the standard window vertical border size including caption bar. /// \sa LApp::GetMetric() LGI_MET_DECOR_Y, /// Get the standard window caption bar height only. /// \sa LApp::GetMetric() LGI_MET_DECOR_CAPTION, /// Get the height of a single line menu bar /// \sa LApp::GetMetric() LGI_MET_MENU, /// This is non-zero if the system is theme aware LGI_MET_THEME_AWARE, /// Size of a window's shadow LGI_MET_WINDOW_SHADOW, }; /// \brief Types of system paths available for querying /// \sa LgiGetSystemPath enum LSystemPath { LSP_ROOT, /// The location of the operating system folder /// [Win32] = e.g. C:\Windows /// [Mac] = /System /// [Linux] /boot LSP_OS, /// The system library folder /// [Win32] = e.g. C:\Windows\System32 /// [Mac] = /Library /// [Linux] = /usr/lib LSP_OS_LIB, /// A folder for storing temporary files. These files are usually /// deleted automatically later by the system. /// [Win32] = ~\Local Settings\Temp /// [Mac] = ~/Library/Caches/TemporaryItems /// [Linux] = /tmp LSP_TEMP, /// System wide application data /// [Win32] = ~\..\All Users\Application Data /// [Mac] = /System/Library /// [Linux] = /usr LSP_COMMON_APP_DATA, /// User specific application data /// [Win32] = ~\Application Data /// [Mac] = ~/Library /// [Linux] = /usr /// [Haiku] = ~/config (???) LSP_USER_APP_DATA, /// Machine + user specific application data (probably should not use) /// [Win32] = ~\Local Settings\Application Data /// [Mac] = ~/Library /// [Linux] = /usr/local LSP_LOCAL_APP_DATA, /// Desktop dir /// i.e. ~/Desktop LSP_DESKTOP, /// Home dir /// i.e. ~ LSP_HOME, /// Application install folder: /// [Win] c:\Program Files /// [Mac] /Applications /// [Linux] /usr/bin LSP_USER_APPS, /// The running application's path. /// [Mac] This doesn't include the "Contents/MacOS" part of the path inside the bundle. LSP_EXE, /// The system trash folder. LSP_TRASH, /// The app's install folder. /// [Win32] = $ExePath (sans '\Release' or '\Debug') /// [Mac/Linux] = $ExePath /// Where $ExePath is the folder the application is running from LSP_APP_INSTALL, /// The app's root folder (Where config and data should be stored) /// LOptionsFile uses LSP_APP_ROOT as the default location. /// [Win32] = ~\Application Data\Roaming\$AppName /// [Mac] = ~/Library/$AppName /// [Linux] = ~/.$AppName /// Where $AppName = LApp::GetName. /// If the given folder doesn't exist it will be created. LSP_APP_ROOT, /// This is the user's documents folder /// [Win32] ~\My Documents /// [Mac] ~\Documents LSP_USER_DOCUMENTS, /// This is the user's music folder /// [Win32] ~\My Music /// [Mac] ~\Music LSP_USER_MUSIC, /// This is the user's video folder /// [Win32] ~\My Videos /// [Mac] ~\Movies LSP_USER_VIDEO, /// This is the user's download folder /// ~\Downloads LSP_USER_DOWNLOADS, /// This is the user's links folder /// [Win32] = ~\Links /// [Mac] = ??? /// [Linux] = ??? LSP_USER_LINKS, /// User's pictures/photos folder LSP_USER_PICTURES, /// [Win32] = C:\Users\%HOME%\Pictures /// [Mac] = ??? /// [Linux] = ~\Pictures LSP_MOUNT_POINT, }; #ifdef _DEBUG #define DeclDebugArgs , const char *_file, int _line #define PassDebugArgs , __FILE__, __LINE__ #else #define DeclDebugArgs #define PassDebugArgs #endif #define _FL __FILE__, __LINE__ #define CALL_MEMBER_FN(obj, memFn) ((obj).*(memFn)) #include "lgi/common/AutoPtr.h" #endif diff --git a/include/lgi/common/Mime.h b/include/lgi/common/Mime.h --- a/include/lgi/common/Mime.h +++ b/include/lgi/common/Mime.h @@ -1,178 +1,178 @@ #pragma once #include "lgi/common/Stream.h" extern void CreateMimeBoundary(char *Buf, int BufLen); // MIME content types enum LMimeEncodings { CONTENT_NONE, CONTENT_BASE64, CONTENT_QUOTED_PRINTABLE, CONTENT_OCTET_STREAM }; class LMime; class LMimeAction { friend class LMime; protected: // Parent ptr LMime *Mime = NULL; public: virtual void Empty() {} // reset to initial state }; class LMimeBuf : public LStringPipe { ssize_t Total = 0; LStreamI *Src = NULL; LStreamEnd *End = NULL; public: constexpr static int BlockSz = 4 << 10; LMimeBuf(LStreamI *src, LStreamEnd *end); // Read some data from 'src' // \returns true if some data was received. bool ReadSrc(); /// Reads 'Len' bytes into a LString LString ReadStr(ssize_t Len = -1) { if (Len < 0) Len = GetSize(); LString s; if (!s.Length(Len)) return s; auto rd = Read(s.Get(), s.Length()); if (rd < 0) s.Empty(); else if (rd < (ssize_t)s.Length()) s.Length(rd); return s; } ssize_t Pop(LArray &Buf) override; ssize_t Pop(char *Str, ssize_t BufSize) override; }; class LMime { // Header info LString Headers; // Data info - ssize_t DataPos; - ssize_t DataSize; - LMutex *DataLock; - LStreamI *DataStore; - bool OwnDataStore; + ssize_t DataPos = 0; + ssize_t DataSize = 0; + LMutex *DataLock = NULL; + LStreamI *DataStore = NULL; + bool OwnDataStore = false; // Other info - char *TmpPath; - LMime *Parent; + char *TmpPath = NULL; + LMime *Parent = NULL; LArray Children; // Private methods bool Lock(); void Unlock(); bool CreateTempData(); char *NewValue(char *&s, bool Alloc = true); char *StartOfField(char *s, const char *Feild); char *NextField(char *s); char *GetTmpPath(); public: static const char *DefaultCharset; LMime(const char *TmpFileRoot = 0); virtual ~LMime(); // Methods bool Insert(LMime *m, int pos = -1); void Remove(); ssize_t Length() { return Children.Length(); } LMime *operator[](uint32_t i); LMime *NewChild(); void DeleteChildren() { Children.DeleteObjects(); } void Empty(); bool SetHeaders(const char *h); const char *GetHeaders() { return Headers; } ssize_t GetLength() { return DataSize; } LStreamI *GetData(bool Detach = false); bool SetData(bool OwnStream, LStreamI *Input, int RdPos = 0, int RdSize = -1, LMutex *Lock = 0); bool SetData(char *Str, int Len); // Simple Header Management char *Get(const char *Field, bool Short = true, const char *Default = 0); // 'Short'=true returns the value with out subfields bool Set(const char *Field, const char *Value); // 'Value' has to include any subfields. char *GetSub(const char *Field, const char *Sub); bool SetSub(const char *Field, const char *Sub, const char *Value, const char *DefaultValue = 0); // Header Shortcuts (uses Get[Sub]/Set[Sub]) char *GetMimeType() { return Get("Content-Type", true, "text/plain"); } bool SetMimeType(const char *s) { return Set("Content-Type", s); } char *GetEncoding() { return Get("Content-Transfer-Encoding"); } bool SetEncoding(const char *s) { return Set("Content-Transfer-Encoding", s); } char *GetCharset() { return GetSub("Content-Type", "Charset"); } bool SetCharset(const char *s) { return SetSub("Content-Type", "Charset", s, DefaultCharset); } char *GetBoundary() { return GetSub("Content-Type", "Boundary"); } bool SetBoundary(const char *s) { return SetSub("Content-Type", "Boundary", s, DefaultCharset); } char *GetFileName(); bool SetFileName(const char *s) { return SetSub("Content-Type", "Name", s, DefaultCharset); } // Streaming class LMimeText { public: class LMimeDecode : public LPullStreamer, public LMimeAction { public: ssize_t Pull(LStreamI *Source, LStreamEnd *End = NULL); ssize_t Parse(LMimeBuf *Source, class ParentState *State = NULL); void Empty(); } Decode; class LMimeEncode : public LPushStreamer, public LMimeAction { public: ssize_t Push(LStreamI *Dest, LStreamEnd *End = NULL); void Empty(); } Encode; } Text; friend class LMime::LMimeText::LMimeDecode; friend class LMime::LMimeText::LMimeEncode; class LMimeBinary { public: class LMimeRead : public LPullStreamer, public LMimeAction { public: ssize_t Pull(LStreamI *Source, LStreamEnd *End = 0); void Empty(); } Read; class LMimeWrite : public LPushStreamer, public LMimeAction { public: int64 GetSize(); ssize_t Push(LStreamI *Dest, LStreamEnd *End = 0); void Empty(); } Write; } Binary; friend class LMime::LMimeBinary::LMimeRead; friend class LMime::LMimeBinary::LMimeWrite; }; diff --git a/include/lgi/common/Window.h b/include/lgi/common/Window.h --- a/include/lgi/common/Window.h +++ b/include/lgi/common/Window.h @@ -1,333 +1,333 @@ #ifndef _LWINDOW_H_ #define _LWINDOW_H_ #include "lgi/common/View.h" /// The available states for a top level window enum LWindowZoom { /// Minimized LZoomMin, /// Restored/Normal LZoomNormal, /// Maximized LZoomMax }; enum LWindowHookType { LNoEvents = 0, /// \sa LWindow::RegisterHook() LMouseEvents = 1, /// \sa LWindow::RegisterHook() LKeyEvents = 2, /// \sa LWindow::RegisterHook() LKeyAndMouseEvents = LMouseEvents | LKeyEvents, }; /// A top level window. class LgiClass LWindow : public LView, // This needs to be second otherwise is causes v-table problems. #ifndef LGI_SDL virtual #endif public LDragDropTarget { friend class BViewRedir; friend class LApp; friend class LView; friend class LButton; friend class LDialog; friend class LWindowPrivate; friend struct LDialogPriv; bool _QuitOnClose = false; protected: class LWindowPrivate *d; #if WINNATIVE LWindow *_Dialog = NULL; #elif defined(HAIKU) LWindowZoom _PrevZoom = LZoomNormal; #else - OsWindow Wnd; + OsWindow Wnd = NULL; void SetDeleteOnClose(bool i); #endif #if defined __GTK_H__ friend class LMenu; friend void lgi_widget_size_allocate(Gtk::GtkWidget *widget, Gtk::GtkAllocation *allocation); Gtk::GtkWidget *_Root, *_VBox, *_MenuBar; void OnGtkDelete(); Gtk::gboolean OnGtkEvent(Gtk::GtkWidget *widget, Gtk::GdkEvent *event); #elif defined(LGI_CARBON) friend pascal OSStatus LgiWindowProc(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData); void _Delete(); bool _RequestClose(bool os); #elif defined(__OBJC__) public: // This returns the root level content NSView NSView *Handle(); protected: #endif /// The default button LViewI *_Default = NULL; /// The menu on the window LMenu *Menu = NULL; void SetChildDialog(LDialog *Dlg); void SetDragHandlers(bool On); /// Haiku: This shuts down the window's thread cleanly. int WaitThread(); public: #ifdef _DEBUG LMemDC DebugDC; #endif #ifdef __GTK_H__ LWindow(Gtk::GtkWidget *w = NULL); #elif LGI_CARBON LWindow(WindowRef wr = NULL); #elif LGI_COCOA LWindow(OsWindow wnd = NULL); #else LWindow(); #endif ~LWindow(); const char *GetClass() override { return "LWindow"; } /// Lays out the child views into the client area. virtual void PourAll(); /// Returns the current menu object LMenu *GetMenu() { return Menu; } /// Set the menu object. void SetMenu(LMenu *m) { Menu = m; } /// Set the window's icon bool SetIcon(const char *FileName); /// Don't show title bar bool SetTitleBar(bool ShowTitleBar); /// Gets the "quit on close" setting. bool GetQuitOnClose() { return _QuitOnClose; } /// \brief Sets the "quit on close" setting. /// /// When this is switched on the application will quit the main message /// loop when this LWindow is closed. This is really useful for your /// main application window. Otherwise the UI will disappear but the /// application is still running. void SetQuitOnClose(bool i) { _QuitOnClose = i; } bool GetSnapToEdge(); void SetSnapToEdge(bool b); bool GetAlwaysOnTop(); void SetAlwaysOnTop(bool b); /// Gets the current zoom setting LWindowZoom GetZoom(); /// Sets the current zoom void SetZoom(LWindowZoom i); /// Raises the window to the top of the stack. void Raise(); /// Moves a top level window on screen. void MoveOnScreen(); /// Moves a top level to the center of the screen void MoveToCenter(); /// Moves a top level window to where the mouse is void MoveToMouse(); /// Moves the window to somewhere on the same screen as 'wnd' bool MoveSameScreen(LViewI *wnd); // Focus setting LViewI *GetFocus(); enum FocusType { GainFocus, LoseFocus, ViewDelete }; void SetFocus(LViewI *ctrl, FocusType type); /// This setting can turn of taking focus when the window is shown. Useful for popups that /// don't want to steal focus from an underlying window. /// The default value is 'true' bool SetWillFocus(bool f); /// Registers a watcher to receive OnView... messages before they /// are passed through to the intended recipient. bool RegisterHook ( /// The target view. LView *Target, /// Combination of: /// #LMouseEvents - Where Target->OnViewMouse(...) is called for each click. /// and /// #LKeyEvents - Where Target->OnViewKey(...) is called for each key. /// OR'd together. LWindowHookType EventType, /// Not implemented int Priority = 0 ); /// Unregisters a hook target bool UnregisterHook(LView *Target); /// Gets the default view LViewI *GetDefault(); /// Sets the default view void SetDefault(LViewI *v); /// Saves/loads the window's state, e.g. position, minimized/maximized etc bool SerializeState ( /// The data store for reading/writing LDom *Store, /// The field name to use for storing settings under const char *FieldName, /// TRUE if loading the settings into the window, FALSE if saving to the store. bool Load ); /// Builds a map of keyboard short cuts. typedef LHashTbl,LViewI*> ShortcutMap; void BuildShortcuts(ShortcutMap &Map, LViewI *v = NULL); ////////////////////// Events /////////////////////////////// /// Called when the window zoom state changes. virtual void OnZoom(LWindowZoom Action) {} /// Called when the tray icon is clicked. (if present) virtual void OnTrayClick(LMouse &m); /// Called when the tray icon menu is about to be displayed. virtual void OnTrayMenu(LSubMenu &m) {} /// Called when the tray icon menu item has been selected. virtual void OnTrayMenuResult(int MenuId) {} /// Called when files are dropped on the window. virtual void OnReceiveFiles(LArray &Files) {} /// Called when a URL is sent to the window virtual void OnUrl(const char *Url) {}; ///////////////// Implementation //////////////////////////// void OnPosChange() override; LMessage::Result OnEvent(LMessage *Msg) override; void OnPaint(LSurface *pDC) override; /// Allow the window to filter mouse events: /// \returns false if the Window consumed the event. bool HandleViewMouse(LView *v, LMouse &m); /// Allow the window to filter key events: /// \returns false if the Window consumed the event. bool HandleViewKey(LView *v, LKey &k); /// Return true to accept application quit bool OnRequestClose(bool OsShuttingDown) override; bool Obscured(); bool Visible() override; void Visible(bool i) override; bool IsActive(); bool SetActive(); LRect &GetPos() override; void SetDecor(bool Visible); LPoint GetDpi(); LPointF GetDpiScale(); void ScaleSizeToDpi(); // D'n'd int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) override; int OnDrop(LArray &Data, LPoint Pt, int KeyState) override; #if !WINNATIVE bool Attach(LViewI *p) override; // Props #if defined(HAIKU) OsWindow WindowHandle() override; #else OsWindow WindowHandle() override { return Wnd; } #endif bool Name(const char *n) override; const char *Name() override; bool SetPos(LRect &p, bool Repaint = false) override; LRect &GetClient(bool InClientSpace = true) override; // Events void OnChildrenChanged(LViewI *Wnd, bool Attaching) override; void OnCreate() override; virtual void OnFrontSwitch(bool b); #else OsWindow WindowHandle() override { return _View; } #endif #if HAIKU void SetModalDialog(LWindow *dlg); #elif defined(LGI_SDL) virtual bool PushWindow(LWindow *v); virtual LWindow *PopWindow(); #elif defined __GTK_H__ void OnGtkRealize(); bool IsAttached(); void Quit(bool DontDelete = false); LRect *GetDecorSize(); bool TranslateMouse(LMouse &m); LViewI *WindowFromPoint(int x, int y, bool Debug = false); void _SetDynamic(bool b); void _OnViewDelete(); void SetParent(LViewI *p) override; #elif defined(MAC) bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0, int64_t TimeoutMs = -1) override; void Quit(bool DontDelete = false) override; int OnCommand(int Cmd, int Event, OsView Wnd) override; LViewI *WindowFromPoint(int x, int y, int DebugDebug = 0) override; #if defined(LGI_CARBON) OSErr HandlerCallback(DragTrackingMessage *tracking, DragRef theDrag); #endif #endif }; #endif diff --git a/linux/Makefile.linux b/linux/Makefile.linux --- a/linux/Makefile.linux +++ b/linux/Makefile.linux @@ -1,229 +1,232 @@ #!/usr/bin/make # # This makefile generated by LgiIde # http://www.memecode.com/lgi.php # .SILENT : CC = gcc CPP = g++ Target = lgi-gtk3 ifndef Build Build = Debug endif MakeDir := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) BuildDir = $(Build) -Flags = -fPIC -fno-inline -fpermissive -Wno-format-truncation +CFlags = -MMD -MP -g -fPIC -fno-inline +CppFlags = $CFlags -fpermissive -std=c++14 ifeq ($(Build),Debug) - Flags += -MMD -MP -g -std=c++14 + CFlags += -g + CppFlags += -g Tag = d Defs = -D_DEBUG -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX -DLGI_LIBRARY Libs = \ -lmagic \ -lappindicator3 \ -lcrypt \ -static-libgcc \ `pkg-config --libs gtk+-3.0` Inc = \ `pkg-config --cflags gtk+-3.0` \ `pkg-config --cflags gstreamer-1.0` \ -I../../../../../usr/include/libappindicator3-0.1 \ -I./private/linux \ -I./private/common \ -I./include/lgi/linux/Gtk \ -I./include/lgi/linux \ -I./include else - Flags += -MMD -MP -s -Os -std=c++14 + CFlags += -s -Os + CppFlags += -s -Os Defs = -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX -DLGI_LIBRARY Libs = \ -lmagic \ -lappindicator3 \ -lcrypt \ -static-libgcc \ `pkg-config --libs gtk+-3.0` Inc = \ `pkg-config --cflags gtk+-3.0` \ `pkg-config --cflags gstreamer-1.0` \ -I../../../../../usr/include/libappindicator3-0.1 \ -I./private/linux \ -I./private/common \ -I./include/lgi/linux/Gtk \ -I./include/lgi/linux \ -I./include endif # Dependencies Source = src/linux/Lgi/Window.cpp \ src/linux/Lgi/Widgets.cpp \ src/linux/Lgi/View.cpp \ src/linux/Lgi/Thread.cpp \ src/linux/Lgi/Printer.cpp \ src/linux/Lgi/Menu.cpp \ src/linux/Lgi/Layout.cpp \ src/linux/Lgi/General.cpp \ src/linux/Lgi/DragAndDrop.cpp \ src/linux/Lgi/ClipBoard.cpp \ src/linux/Lgi/App.cpp \ src/linux/Gtk/ScreenDC.cpp \ src/linux/Gtk/PrintDC.cpp \ src/linux/Gtk/MemDC.cpp \ src/linux/Gtk/LgiWidget.cpp \ src/linux/Gtk/Gdc2.cpp \ src/linux/General/ShowFileProp_Linux.cpp \ src/linux/General/Mem.cpp \ src/linux/General/File.cpp \ src/common/Widgets/Tree.cpp \ src/common/Widgets/ToolBar.cpp \ src/common/Widgets/TextLabel.cpp \ src/common/Widgets/TabView.cpp \ src/common/Widgets/TableLayout.cpp \ src/common/Widgets/StatusBar.cpp \ src/common/Widgets/Splitter.cpp \ src/common/Widgets/Slider.cpp \ src/common/Widgets/ScrollBar.cpp \ src/common/Widgets/RadioGroup.cpp \ src/common/Widgets/ProgressDlg.cpp \ src/common/Widgets/Progress.cpp \ src/common/Widgets/Popup.cpp \ src/common/Widgets/Panel.cpp \ src/common/Widgets/List.cpp \ src/common/Widgets/ItemContainer.cpp \ src/common/Widgets/Edit.cpp \ src/common/Widgets/Combo.cpp \ src/common/Widgets/CheckBox.cpp \ src/common/Widgets/Button.cpp \ src/common/Widgets/Box.cpp \ src/common/Widgets/Bitmap.cpp \ src/common/Text/XmlTree.cpp \ src/common/Text/Utf8.cpp \ src/common/Text/Unicode.cpp \ src/common/Text/Token.cpp \ src/common/Text/TextView3.cpp \ src/common/Text/String.cpp \ src/common/Text/DocView.cpp \ src/common/Skins/Gel/Gel.cpp \ src/common/Resource/Res.cpp \ src/common/Resource/LgiRes.cpp \ src/common/Net/Uri.cpp \ src/common/Net/Net.cpp \ src/common/Net/MDStringToDigest.cpp \ src/common/Net/Base64.cpp \ src/common/Lgi/WindowCommon.cpp \ src/common/Lgi/ViewCommon.cpp \ src/common/Lgi/Variant.cpp \ src/common/Lgi/TrayIcon.cpp \ src/common/Lgi/ToolTip.cpp \ src/common/Lgi/ThreadEvent.cpp \ src/common/Lgi/ThreadCommon.cpp \ src/common/Lgi/SubProcess.cpp \ src/common/Lgi/Stream.cpp \ src/common/Lgi/Rand.cpp \ src/common/Lgi/OptionsFile.cpp \ src/common/Lgi/Object.cpp \ src/common/Lgi/Mutex.cpp \ src/common/Lgi/Mru.cpp \ src/common/Lgi/MenuCommon.cpp \ src/common/Lgi/MemStream.cpp \ src/common/Lgi/LMsg.cpp \ src/common/Lgi/Library.cpp \ src/common/Lgi/LgiCommon.cpp \ src/common/Lgi/Input.cpp \ src/common/Lgi/GuiUtils.cpp \ src/common/Lgi/FontSelect.cpp \ src/common/Lgi/FindReplace.cpp \ src/common/Lgi/FileSelect.cpp \ src/common/Lgi/DragAndDropCommon.cpp \ src/common/Lgi/DataDlg.cpp \ src/common/Lgi/CssTools.cpp \ src/common/Lgi/Css.cpp \ src/common/Lgi/AppCommon.cpp \ src/common/Lgi/Alert.cpp \ src/common/Hash/sha1/sha1.c \ src/common/Hash/md5/md5.c \ src/common/General/Properties.cpp \ src/common/General/Password.cpp \ src/common/General/FileCommon.cpp \ src/common/General/ExeCheck.cpp \ src/common/General/DateTime.cpp \ src/common/General/Containers.cpp \ src/common/Gdc2/Tools/GdcTools.cpp \ src/common/Gdc2/Tools/ColourReduce.cpp \ src/common/Gdc2/Surface.cpp \ src/common/Gdc2/Rect.cpp \ src/common/Gdc2/Path/Path.cpp \ src/common/Gdc2/GdcCommon.cpp \ src/common/Gdc2/Font/TypeFace.cpp \ src/common/Gdc2/Font/StringLayout.cpp \ src/common/Gdc2/Font/FontType.cpp \ src/common/Gdc2/Font/FontSystem.cpp \ src/common/Gdc2/Font/Font.cpp \ src/common/Gdc2/Font/DisplayString.cpp \ src/common/Gdc2/Font/Charset.cpp \ src/common/Gdc2/Filters/Filter.cpp \ src/common/Gdc2/Colour.cpp \ src/common/Gdc2/Alpha.cpp \ src/common/Gdc2/8Bit.cpp \ src/common/Gdc2/32Bit.cpp \ src/common/Gdc2/24Bit.cpp \ src/common/Gdc2/16Bit.cpp \ src/common/Gdc2/15Bit.cpp SourceC := $(filter %.c,$(Source)) ObjectsC := $(SourceC:.c=.o) SourceCpp := $(filter %.cpp,$(Source)) ObjectsCpp := $(SourceCpp:.cpp=.o) Objects := $(notdir $(ObjectsC) $(ObjectsCpp)) Objects := $(addprefix $(BuildDir)/,$(Objects)) Deps := $(patsubst %.o,%.d,$(Objects)) $(BuildDir)/%.o: %.c mkdir -p $(@D) echo $(notdir $<) [$(Build)] - $(CC) $(Inc) $(Flags) $(Defs) -c $< -o $@ + $(CC) $(Inc) $(CFlags) $(Defs) -c $< -o $@ $(BuildDir)/%.o: %.cpp mkdir -p $(@D) echo $(notdir $<) [$(Build)] - $(CPP) $(Inc) $(Flags) $(Defs) -c $< -o $@ + $(CPP) $(Inc) $(CppFlags) $(Defs) -c $< -o $@ # Target TargetFile = lib$(Target)$(Tag).so $(TargetFile) : $(Objects) mkdir -p $(BuildDir) @echo Linking $(TargetFile) [$(Build)]... $(CPP)$s -shared \ \ -o $(BuildDir)/$(TargetFile) \ $(Objects) \ $(Libs) @echo Done. -include $(Objects:.o=.d) # Clean out targets clean : rm -rf $(BuildDir) @echo Cleaned $(BuildDir) VPATH=$(BuildDir) \ ./src/linux/Lgi \ ./src/linux/Gtk \ ./src/linux/General \ ./src/common/Widgets \ ./src/common/Text \ ./src/common/Skins/Gel \ ./src/common/Resource \ ./src/common/Net \ ./src/common/Lgi \ ./src/common/Hash/sha1 \ ./src/common/Hash/md5 \ ./src/common/General \ ./src/common/Gdc2/Tools \ ./src/common/Gdc2/Path \ ./src/common/Gdc2/Font \ ./src/common/Gdc2/Filters \ ./src/common/Gdc2 diff --git a/src/common/Net/Mime.cpp b/src/common/Net/Mime.cpp --- a/src/common/Net/Mime.cpp +++ b/src/common/Net/Mime.cpp @@ -1,1692 +1,1684 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/Mime.h" #include "lgi/common/Base64.h" #define DEBUG_MIME 0 #if DEBUG_MIME #define LOG(...) printf(__VA_ARGS__) #else #define LOG(...) #endif static const char *MimeEol = "\r\n"; static const char *MimeWs = " \t\r\n"; static const char *MimeStr = "\'\""; static const char *MimeQuotedPrintable = "quoted-printable"; static const char *MimeBase64 = "base64"; #define MimeMagic ( ('M'<<24) | ('I'<<24) | ('M'<<24) | ('E'<<24) ) #define SkipWs(s) while (*s && strchr(MimeWs, *s)) s++ #define SkipNonWs(s) while (*s && !strchr(MimeWs, *s)) s++ const char *LMime::DefaultCharset = "text/plain"; template int CastInt(T in) { int out = (int)in; LAssert(out == in); return out; } int AltScore(char *Mt) { int Score = 0; if (Mt) { if (stristr(Mt, "/html")) Score = 1; else if (stristr(Mt, "/related")) Score = 2; } // printf("Score '%s' = %i\n", Mt, Score); DeleteArray(Mt); return Score; } int AltSortCmp(LMime **a, LMime **b) { int a_score = AltScore((*a)->GetMimeType()); int b_score = AltScore((*b)->GetMimeType()); return a_score - b_score; } /////////////////////////////////////////////////////////////////////// enum MimeBoundary { MimeData = 0, MimeNextSeg = 1, MimeEndSeg = 2 }; MimeBoundary IsMimeBoundary(char *Boundary, char *Line) { if (!Boundary || !Line) return MimeData; auto BoundaryLen = strlen(Boundary); if (Line[0] != '-' || Line[1] != '-') return MimeData; if (strncmp(Line + 2, Boundary, BoundaryLen) == 0) { printf("matched prefix: %s\n", Line); // MIME segment boundary Line += 2 + BoundaryLen; if (Line[0] == '-' && Line[1] == '-') { return MimeEndSeg; } else { return MimeNextSeg; } } else { printf("no match: line='%s' doesn't match boundry='%s'\n", Line + 2, Boundary); } return MimeData; } void CreateMimeBoundary(char *Buf, int BufLen) { if (Buf) { static int Count = 1; sprintf_s(Buf, BufLen, "--%x-%x-%x--", (int)LCurrentTime(), (int)(uint64)LGetCurrentThread(), Count++); } } /////////////////////////////////////////////////////////////////////// class LCoderStream : public LStream { protected: LStreamI *Out; public: LCoderStream(LStreamI *o) { Out = o; } }; class LMimeTextEncode : public LCoderStream { // This code needs to make sure it writes an end-of-line at // the end, otherwise a following MIME boundary could be missed. bool LastEol; public: LMimeTextEncode(LStreamI *o) : LCoderStream(o) { LastEol = false; } ~LMimeTextEncode() { if (!LastEol) { #ifdef _DEBUG ssize_t w = #endif Out->Write(MimeEol, 2); LAssert(w == 2); } } ssize_t Write(const void *p, ssize_t size, int f = 0) { // Make sure any new lines are \r\n char *s = (char*)p, *e = s + size; ssize_t wr = 0; while (s < e) { char *c = s; while (c < e && *c != '\r' && *c != '\n') c++; if (c > s) { ptrdiff_t bytes = c - s; ssize_t w = Out->Write(s, (int)bytes); if (w != bytes) return wr; wr += w; LastEol = false; } while (c < e && (*c == '\r' || *c == '\n')) { if (*c == '\n') { ssize_t w = Out->Write(MimeEol, 2); if (w != 2) return wr; LastEol = true; } wr++; c++; } s = c; } return wr; } }; class LMimeQuotedPrintableEncode : public LCoderStream { // 'd' is our current position in 'Buf' char *d; // A buffer for a line of quoted printable text char Buf[128]; public: LMimeQuotedPrintableEncode(LStreamI *o) : LCoderStream(o) { Buf[0] = 0; d = Buf; } ~LMimeQuotedPrintableEncode() { if (d > Buf) { // Write partial line *d++ = '\r'; *d++ = '\n'; ptrdiff_t Len = d - Buf; Out->Write(Buf, CastInt(Len)); } } ssize_t Write(const void *p, ssize_t size, int f = 0) { char *s = (char*)p; char *e = s + size; while (s < e) { if (*s == '\n' || !*s) { *d++ = '\r'; *d++ = '\n'; ptrdiff_t Len = d - Buf; if (Out->Write(Buf, CastInt(Len)) < Len) { LAssert(!"write error"); break; } if (!*s) { break; } d = Buf; s++; } else if (*s & 0x80 || *s == '.' || *s == '=') { int Ch = sprintf_s(d, sizeof(Buf)-(d-Buf), "=%2.2X", (uchar)*s); if (Ch < 0) { LAssert(!"printf error"); break; } d += Ch; s++; } else if (*s != '\r') { *d++ = *s++; } else { // Consume any '\r' without outputting them s++; } if (d-Buf > 73) { // time for a new line. *d++ = '='; *d++ = '\r'; *d++ = '\n'; ptrdiff_t Len = d-Buf; if (Out->Write(Buf, CastInt(Len)) < Len) { LAssert(!"write error"); break; } d = Buf; } } return CastInt(s - (const char*)p); } }; class LMimeQuotedPrintableDecode : public LCoderStream { uint8_t ConvHexToBin(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c + 10 - 'a'; if (c >= 'A' && c <= 'F') return c + 10 - 'A'; return 0; } public: LMimeQuotedPrintableDecode(LStreamI *o) : LCoderStream(o) {} ssize_t Write(const void *p, ssize_t size, int f = 0) { ssize_t Written = 0; char Line[1024]; char *o = Line; const char *s = (const char*) p; const char *e = s + size; #define NEXT(ptr) if (++ptr >= e) break for (const char *s = (const char*)p; s < e; ) { if (*s == '=') { NEXT(s); // skip '=' if (*s == '\r' || *s == '\n') { if (*s == '\r') NEXT(s); if (*s == '\n') NEXT(s); } else if (*s) { uint8_t hi = ConvHexToBin(*s++); if (s >= e) break; uint8_t low = ConvHexToBin(*s++); if (s >= e) break; *o++ = (hi << 4) | (low & 0xF); } else break; } else { *o++ = *s++; } if (o - Line > 1000) { auto w = Out->Write(Line, o - Line); if (w > 0) { Written += w; o = Line; } else break; // Error } } if (o > Line) { auto w = Out->Write(Line, o - Line); if (w > 0) Written += w; } return Written; } }; #define BASE64_LINE_SZ 76 #define BASE64_READ_SZ (BASE64_LINE_SZ*3/4) class LMimeBase64Encode : public LCoderStream { LMemQueue Buf; public: LMimeBase64Encode(LStreamI *o) : LCoderStream(o) {} ~LMimeBase64Encode() { uchar b[100]; int64 Len = Buf.GetSize(); LAssert(Len < sizeof(b)); ssize_t r = Buf.Read(b, CastInt(Len)); if (r > 0) { char t[256]; ssize_t w = ConvertBinaryToBase64(t, sizeof(t), b, r); Out->Write(t, w); Out->Write(MimeEol, 2); } } ssize_t Write(const void *p, ssize_t size, int f = 0) { Buf.Write((uchar*)p, size); int64 Sz; while ((Sz = Buf.GetSize()) >= BASE64_READ_SZ) { uchar b[100]; ssize_t r = Buf.Read(b, BASE64_READ_SZ); if (r) { char t[256]; ssize_t w = ConvertBinaryToBase64(t, sizeof(t), b, r); if (w > 0) { Out->Write(t, w); Out->Write(MimeEol, 2); } else LAssert(0); } else return 0; } return size; } }; class LMimeBase64Decode : public LCoderStream { LStringPipe Buf; uint8_t Lut[256]; public: LMimeBase64Decode(LStreamI *o) : LCoderStream(o) { ZeroObj(Lut); memset(Lut+(int)'a', 1, 'z'-'a'+1); memset(Lut+(int)'A', 1, 'Z'-'A'+1); memset(Lut+(int)'0', 1, '9'-'0'+1); Lut[(int)'+'] = 1; Lut[(int)'/'] = 1; Lut[(int)'='] = 1; } ssize_t Write(const void *p, ssize_t size, int f = 0) { ssize_t Status = 0; // Push non whitespace into the memory buf char *s = (char*)p; char *e = s + size; while (s < e) { while (*s && s < e && !Lut[(int)*s]) s++; char *Start = s; while (*s && s < e && Lut[(int)*s]) s++; if (s-Start > 0) Buf.Push(Start, CastInt(s-Start)); else break; } // While there is at least one run of base64 (4 bytes) convert it to text // and write it to the output stream int Size; while ((Size = CastInt(Buf.GetSize())) > 3) { Size &= ~3; char t[256]; ssize_t r = MIN(sizeof(t), Size); if ((r = Buf.LMemQueue::Read((uchar*)t, r)) > 0) { uchar b[256]; ssize_t w = ConvertBase64ToBinary(b, sizeof(b), t, r); Out->Write(b, w); Status += w; } } return Status; } }; /////////////////////////////////////////////////////////////////////// LMimeBuf::LMimeBuf(LStreamI *src, LStreamEnd *end) : LStringPipe(BlockSz) { Src = src; End = end; if (Src) Src->SetPos(0); } // Read some data from 'src' // \returns true if some data was received. bool LMimeBuf::ReadSrc() { if (!Src) return false; auto b = GetBuffer(); if (!b) return false; auto rd = Src->Read(b.ptr, b.len); if (rd <= 0) return false; b.Commit(rd); return true; } ssize_t LMimeBuf::Pop(LArray &Out) { ssize_t Ret = 0; while (!(Ret = LStringPipe::Pop(Out))) { if (Src) { char Buf[2048]; ssize_t r = Src ? Src->Read(Buf, sizeof(Buf)) : 0; if (r) { if (End) { ssize_t e = End->IsEnd(Buf, r); if (e >= 0) { // End of stream ssize_t s = e - Total; Push(Buf, s); Total += s; Src = 0; // no more data anyway } else { // Not the end Push(Buf, r); Total += r; } } else { Push(Buf, r); Total += r; } } else { Src = NULL; // Source data is finished } } else { // Is there any unterminated space in the string pipe? int64 Sz = LStringPipe::GetSize(); if (Sz > 0) { if ((int64)Out.Length() < Sz) Out.Length(Sz); Ret = LStringPipe::Read(Out.AddressOf(), Sz); } break; } } return Ret; } ssize_t LMimeBuf::Pop(char *Str, ssize_t BufSize) { ssize_t Ret = 0; while (!(Ret = LStringPipe::Pop(Str, BufSize))) { if (Src) { char Buf[1024]; ssize_t r = Src ? Src->Read(Buf, sizeof(Buf)) : 0; if (r) { if (End) { ssize_t e = End->IsEnd(Buf, r); if (e >= 0) { // End of stream ssize_t s = e - Total; Push(Buf, s); Total += s; Src = 0; // no more data anyway } else { // Not the end Push(Buf, r); Total += r; } } else { Push(Buf, r); Total += r; } } else { Src = NULL; // Source data is finished } } else { // Is there any unterminated space in the string pipe? int64 Sz = LStringPipe::GetSize(); if (Sz > 0) { Ret = LStringPipe::Read(Str, BufSize); } break; } } return Ret; } /////////////////////////////////////////////////////////////////////// // Mime Object LMime::LMime(const char *tmp) { - Parent = 0; TmpPath = NewStr(tmp); - - DataPos = 0; - DataSize = 0; - DataLock = 0; - DataStore = 0; - OwnDataStore = 0; - Text.Decode.Mime = this; Text.Encode.Mime = this; Binary.Read.Mime = this; Binary.Write.Mime = this; } LMime::~LMime() { Remove(); Empty(); DeleteArray(TmpPath); } char *LMime::GetFileName() { char *n = GetSub("Content-Type", "Name"); if (!n) n = GetSub("Content-Disposition", "Filename"); if (!n) { n = Get("Content-Location"); if (n) { char *trim = TrimStr(n, "\'\""); if (trim) { DeleteArray(n); n = trim; } } } return n; } LMime *LMime::NewChild() { LMime *n = new LMime(GetTmpPath()); if (n) Insert(n); return n; } bool LMime::Insert(LMime *m, int Pos) { LAssert(m != NULL); if (!m) return false; if (m->Parent) { LAssert(m->Parent->Children.HasItem(m)); m->Parent->Children.Delete(m, true); } m->Parent = this; LAssert(!Children.HasItem(m)); if (Pos >= 0) Children.AddAt(Pos, m); else Children.Add(m); return true; } void LMime::Remove() { if (Parent) { LAssert(Parent->Children.HasItem(this)); Parent->Children.Delete(this, true); Parent = 0; } } LMime *LMime::operator[](uint32_t i) { if (i >= Children.Length()) return 0; return Children[i]; } char *LMime::GetTmpPath() { for (LMime *m = this; m; m = m->Parent) { if (m->TmpPath) return m->TmpPath; } return 0; } bool LMime::SetHeaders(const char *h) { Headers = h; return Headers != 0; } bool LMime::Lock() { bool Lock = true; if (DataLock) { Lock = DataLock->Lock(_FL); } return Lock; } void LMime::Unlock() { if (DataLock) { DataLock->Unlock(); } } bool LMime::CreateTempData() { bool Status = false; DataPos = 0; DataSize = 0; OwnDataStore = true; if ((DataStore = new LTempStream(GetTmpPath(), 4 << 20))) { Status = true; } return Status; } LStreamI *LMime::GetData(bool Detach) { LStreamI *Ds = DataStore; if (Ds) { Ds->SetPos(DataPos); if (Detach) { LOG("%s:%i - Detaching %p from %p\n", _FL, DataStore, this); DataStore = 0; } } return Ds; } bool LMime::SetData(bool OwnStream, LStreamI *d, int Pos, int Size, LMutex *l) { if (DataStore && Lock()) { DeleteObj(DataStore); Unlock(); } if (d) { OwnDataStore = OwnStream; DataPos = Pos; DataSize = Size >= 0 ? Size : (int)d->GetSize(); DataLock = l; DataStore = d; } return true; } bool LMime::SetData(char *Str, int Len) { if (DataStore && Lock()) { DeleteObj(DataStore); Unlock(); } if (Str) { if (Len < 0) { Len = (int)strlen(Str); } DataLock = 0; DataPos = 0; DataSize = Len; DataStore = new LTempStream(GetTmpPath(), 4 << 20); if (DataStore) { DataStore->Write(Str, Len); } } return true; } void LMime::Empty() { if (OwnDataStore) { if (Lock()) { DeleteObj(DataStore); Unlock(); } } while (Children.Length()) delete Children[0]; Headers.Empty(); DataPos = 0; DataSize = 0; DataLock = 0; DataStore = 0; OwnDataStore = 0; } char *LMime::NewValue(char *&s, bool Alloc) { char *Status = 0; int Inc = 0; char *End; if (strchr(MimeStr, *s)) { // Delimited string char Delim = *s++; End = strchr(s, Delim); Inc = 1; } else { // Raw string End = s; while (*End && *End != ';' && *End != '\n' && *End != '\r') End++; while (strchr(MimeWs, End[-1])) End--; } if (End) { if (Alloc) { Status = NewStr(s, End-s); } s = End + Inc; } SkipWs(s); return Status; } char *LMime::StartOfField(char *s, const char *Field) { if (s && Field) { size_t FieldLen = strlen(Field); while (s && *s) { if (strchr(MimeWs, *s)) { s = strchr(s, '\n'); if (s) s++; } else { char *f = s; while (*s && *s != ':' && !strchr(MimeWs, *s)) s++; int fLen = CastInt(s - f); if (*s++ == ':' && fLen == FieldLen && _strnicmp(f, Field, FieldLen) == 0) { return f; break; } else { s = strchr(s, '\n'); if (s) s++; } } } } return 0; } char *LMime::NextField(char *s) { while (s) { while (*s && *s != '\n') s++; if (*s == '\n') { s++; if (!strchr(MimeWs, *s)) { break; } } else { break; } } return s; } char *LMime::Get(const char *Name, bool Short, const char *Default) { char *Status = 0; if (Name && Headers) { char *s = StartOfField(Headers, Name); if (s) { s = strchr(s, ':'); if (s) { s++; SkipWs(s); if (Short) { Status = NewValue(s); } else { char *e = NextField(s); while (strchr(MimeWs, e[-1])) e--; Status = NewStr(s, e-s); } } } if (!Status && Default) { Status = NewStr(Default); } } return Status; } bool LMime::Set(const char *Name, const char *Value) { if (!Name) return false; LStringPipe p; char *h = Headers; if (h) { char *f = StartOfField(h, Name); if (f) { // 'Name' exists, push out pre 'Name' header text p.Push(h, CastInt(f - Headers)); h = NextField(f); } else { if (!Value) { // Nothing to do here... return true; } // 'Name' doesn't exist, push out all the headers p.Push(Headers); h = 0; } } if (Value) { // Push new field int Vlen = CastInt(strlen(Value)); while (Vlen > 0 && strchr(MimeWs, Value[Vlen-1])) Vlen--; p.Push(Name); p.Push(": "); p.Push(Value, Vlen); p.Push(MimeEol); } // else we're deleting the feild if (h) { // Push out any header text post the 'Name' field. p.Push(h); } Headers = p.NewLStr(); return Headers != NULL; } char *LMime::GetSub(const char *Field, const char *Sub) { if (!Field || !Sub) return NULL; auto v = Get(Field, false); if (!v) return NULL; auto SubLen = strlen(Sub); char *Status = NULL; // Move past the field value into the sub fields char *s = v; SkipWs(s); while (*s && *s != ';' && !strchr(MimeWs, *s)) s++; SkipWs(s); while (s && *s++ == ';') { // Parse each name=value pair SkipWs(s); auto Name = s; while (*s && *s != '=' && !strchr(MimeWs, *s)) s++; auto NameLen = s - Name; SkipWs(s); // printf("found field '%.*s'\n", (int)NameLen, Name); if (*s++ == '=') { bool Found = SubLen == NameLen && _strnicmp(Name, Sub, NameLen) == 0; SkipWs(s); Status = NewValue(s, Found); if (Found) break; } else break; } DeleteArray(v); return Status; } bool LMime::SetSub(const char *Field, const char *Sub, const char *Value, const char *DefaultValue) { if (Field && Sub) { char Buf[256]; char *s = StartOfField(Headers, Field); if (s) { // Header already exists s = strchr(s, ':'); if (s++) { SkipWs(s); LStringPipe p; // Push the field data char *e = s; while (*e && !strchr("; \t\r\n", *e)) e++; p.Push(s, CastInt(e-s)); SkipWs(e); // Loop through the subfields and push all those that are not 'Sub' s = e; while (*s++ == ';') { SkipWs(s); char *e = s; while (*e && *e != '=' && !strchr(MimeWs, *e)) e++; char *Name = NewStr(s, e-s); if (Name) { s = e; SkipWs(s); if (*s++ == '=') { char *v = NewValue(s); if (_stricmp(Name, Sub) != 0) { sprintf_s(Buf, sizeof(Buf), ";\r\n\t%s=\"%s\"", Name, v); p.Push(Buf); } DeleteArray(v); } else break; } else break; } if (Value) { // Push the new sub field sprintf_s(Buf, sizeof(Buf), ";\r\n\t%s=\"%s\"", Sub, Value); p.Push(Buf); } char *Data = p.NewStr(); if (Data) { Set(Field, Data); DeleteArray(Data); } } } else if (DefaultValue) { // Header doesn't exist at all if (Value) { // Set sprintf_s(Buf, sizeof(Buf), "%s;\r\n\t%s=\"%s\"", DefaultValue, Sub, Value); return Set(Field, Buf); } else { // Remove return Set(Field, DefaultValue); } } } return false; } ///////////////////////////////////////////////////////////////////////// // Mime Text Conversion // Rfc822 Text -> Object ssize_t LMime::LMimeText::LMimeDecode::Pull(LStreamI *Source, LStreamEnd *End) { LMimeBuf Buf(Source, End); // Stream -> Lines return Parse(&Buf); } class ParentState { public: char *Boundary = NULL; MimeBoundary Type; ParentState() { Type = MimeData; } }; ssize_t LMime::LMimeText::LMimeDecode::Parse(LMimeBuf *Source, ParentState *State) { ssize_t Status = 0; if (!Mime || !Source) { LOG("%s:%i - Arg error %p %p.\n", _FL, Mime, Source); return Status; } Mime->Empty(); if (!Mime->CreateTempData()) { LOG("CreateTempData failed.\n"); LAssert(!"CreateTempData failed."); return Status; } LAssert(Mime->DataStore != NULL); // Read the headers.. if (Buffer.Length() == 0) Buffer.Length(1 << 10); LOG("%s:%i - Reading headers...\n", _FL); ssize_t r; while ((r = Source->Find("\r\n\r\n")) < 0) { if (!Source->ReadSrc()) break; } if (r < 0) // No break between headers and body found. return Status; // Not an error Mime->Headers = Source->ReadStr(r + 4); LOG("%s:%i - Mime->Headers=%i\n", _FL, Mime->Headers?(int)Mime->Headers.Length():-1); // Get various bits out of the header LAutoString Encoding(Mime->GetEncoding()); LAutoString Boundary(Mime->GetBoundary()); LAutoString MimeType(Mime->GetMimeType()); LOG("%s:%i - Encoding=%s, MimeType=%s, Boundary=%s\n", _FL, Encoding.Get(), MimeType.Get(), Boundary.Get()); LStream *Decoder = 0; if (Encoding) { if (_stricmp(Encoding, MimeQuotedPrintable) == 0) { Decoder = new LMimeQuotedPrintableDecode(Mime->DataStore); LOG("%s:%i - Using LMimeQuotedPrintableDecode\n", _FL); } else if (_stricmp(Encoding, MimeBase64) == 0) { Decoder = new LMimeBase64Decode(Mime->DataStore); LOG("%s:%i - Using LMimeBase64Decode\n", _FL); } else { LOG("%s:%i - Unknown encoding '%s'\n", _FL, Encoding); } } Encoding.Reset(); // Read in the rest of the MIME segment bool Done = false; // int64 StartPos = Mime->DataStore->GetPos(); while (!Done) { // Process existing lines ssize_t Len; ssize_t Written = 0; Status = true; while ((Len = Source->Pop(Buffer)) > 0) { // Check for boundary MimeBoundary Type = MimeData; auto b = Buffer.AddressOf(); if (Boundary) { bool CouldBe = Buffer.Length() > 2 && b[0] == '-' && b[1] == '-'; Type = IsMimeBoundary(Boundary, b); if (Type) { LOG("%s:%i - IsMimeBoundary=%i\n", _FL, Type); } else if (CouldBe) { LOG("%s:%i - CouldBe '%s'\n", _FL, b); } } if (State) { State->Type = IsMimeBoundary(State->Boundary, b); if (State->Type) { LOG("%s:%i - IsMimeBoundary=%i\n", _FL, State->Type); Status = Done = true; break; } } DoSegment: if (Type == MimeNextSeg) { ParentState MyState; MyState.Boundary = Boundary; LMime *Seg = new LMime(Mime->GetTmpPath()); if (Seg && Seg->Text.Decode.Parse(Source, &MyState)) { LOG("%s:%i - Inserting child seg.\n", _FL); Mime->Insert(Seg); if (MyState.Type) { Type = MyState.Type; goto DoSegment; } } else { LOG("%s:%i - Text.Decode.Parse failed.\n", _FL); break; } } else if (Type == MimeEndSeg) { Done = true; LOG("%s:%i - MimeEndSeg.\n", _FL); break; } else { // Process data if (Decoder) { Written += Decoder->Write(Buffer.AddressOf(), Len); } else { ssize_t w = Mime->DataStore->Write(Buffer.AddressOf(), Len); if (w > 0) { Written += w; } else { LOG("%s:%i - w 0\n", _FL); Done = true; Status = false; break; } } } } Mime->DataSize = Written; if (Len == 0) { LOG("%s:%i - Len 0\n", _FL); Done = true; } } LOG("%s:%i - Finished\n", _FL); return Status; } void LMime::LMimeText::LMimeDecode::Empty() { } // Object -> Rfc822 Text ssize_t LMime::LMimeText::LMimeEncode::Push(LStreamI *Dest, LStreamEnd *End) { int Status = 0; if (Mime) { char Buf[1024]; int Ch; // Check boundary char *Boundary = Mime->GetBoundary(); if (Mime->Children.Length()) { // Boundary required if (!Boundary) { // Create one char b[256]; CreateMimeBoundary(b, sizeof(b)); Mime->SetBoundary(b); Boundary = Mime->GetBoundary(); } } else if (Boundary) { // Remove boundary Mime->SetBoundary(0); DeleteArray(Boundary); } // Check encoding char *Encoding = Mime->GetEncoding(); if (!Encoding) { // Detect an appropriate encoding int MaxLine = 0; bool Has8Bit = false; bool HasBin = false; if (Mime->DataStore && Mime->Lock()) { Mime->DataStore->SetPos(Mime->DataPos); int x = 0; for (ssize_t i=0; iDataSize; ) { ssize_t m = MIN(Mime->DataSize - i, sizeof(Buf)); ssize_t r = Mime->DataStore->Read(Buf, m); if (r > 0) { for (int n=0; nUnlock(); } if (HasBin) { Encoding = NewStr(MimeBase64); } else if (Has8Bit || MaxLine > 70) { Encoding = NewStr(MimeQuotedPrintable); } if (Encoding) { Mime->SetEncoding(Encoding); } } // Write the headers auto h = Mime->Headers.SplitDelimit(MimeEol); for (unsigned i=0; iWrite(h[i], CastInt(strlen(h[i]))); Dest->Write(MimeEol, 2); } Dest->Write(MimeEol, 2); // Write data LStream *Encoder = 0; if (Encoding) { if (_stricmp(Encoding, MimeQuotedPrintable) == 0) { Encoder = new LMimeQuotedPrintableEncode(Dest); } else if (_stricmp(Encoding, MimeBase64) == 0) { Encoder = new LMimeBase64Encode(Dest); } } if (!Encoder) { Encoder = new LMimeTextEncode(Dest); } if (Mime->DataStore) { if (Mime->Lock()) { Mime->DataStore->SetPos(Mime->DataPos); Status = Mime->DataSize == 0; // Nothing is a valid segment?? for (int i=0; iDataSize; ) { ssize_t m = MIN(Mime->DataSize-i, sizeof(Buf)); ssize_t r = Mime->DataStore->Read(Buf, m); if (r > 0) { Encoder->Write(Buf, r); Status = true; } else break; } Mime->Unlock(); } } else { Status = true; } DeleteObj(Encoder); // Write children if (Mime->Children.Length() && Boundary) { LAutoString Mt(Mime->GetMimeType()); if (Mt && !_stricmp(Mt, "multipart/alternative")) { // Sort the children to order richer content at the bottom... Mime->Children.Sort(AltSortCmp); } for (unsigned i=0; iChildren.Length(); i++) { Ch = sprintf_s(Buf, sizeof(Buf), "--%s\r\n", Boundary); Dest->Write(Buf, Ch); if (!Mime->Children[i]->Text.Encode.Push(Dest, End)) { break; } Status = 1; } Ch = sprintf_s(Buf, sizeof(Buf), "--%s--\r\n", Boundary); Dest->Write(Buf, Ch); } // Clean up DeleteArray(Encoding); DeleteArray(Boundary); } return Status; } void LMime::LMimeText::LMimeEncode::Empty() { } ///////////////////////////////////////////////////////////////////////// // Mime Binary Serialization // Source -> Object ssize_t LMime::LMimeBinary::LMimeRead::Pull(LStreamI *Source, LStreamEnd *End) { if (Source) { int32 Header[4]; Mime->Empty(); // Read header block (Magic, HeaderSize, DataSize, # of Children) // and check magic if (Source->Read(Header, sizeof(Header)) == sizeof(Header) && Header[0] == MimeMagic) { // Read header data Mime->Headers.Length(Header[1]+1); if (Mime->Headers && Source->Read(Mime->Headers, Header[1]) == Header[1]) { // NUL terminate Mime->Headers.Get()[Mime->Headers.Length()] = 0; // Skip body data if (Source->SetPos(Source->GetPos() + Header[2]) > 0) { // Read the children in for (int i=0; iGetTmpPath()); if (c && c->Binary.Read.Pull(Source, End)) { Mime->Insert(c); } else break; } } } return 1; // success } } return 0; // failure } void LMime::LMimeBinary::LMimeRead::Empty() { } // Object -> Dest int64 LMime::LMimeBinary::LMimeWrite::GetSize() { int64 Size = 0; if (Mime) { Size = (sizeof(int32) * 4) + // Header magic + block sizes (Mime->Headers ? Mime->Headers.Length() : 0) + // Headers (Mime->DataStore ? Mime->DataSize : 0); // Data // Children for (unsigned i=0; iChildren.Length(); i++) { Size += Mime->Children[i]->Binary.Write.GetSize(); } } return Size; } ssize_t LMime::LMimeBinary::LMimeWrite::Push(LStreamI *Dest, LStreamEnd *End) { if (Dest && Mime) { int32 Header[4] = { MimeMagic, Mime->Headers ? (int32)Mime->Headers.Length() : 0, Mime->DataStore ? (int32)Mime->DataSize : 0, (int32) Mime->Children.Length() }; if (Dest->Write(Header, sizeof(Header)) == sizeof(Header)) { if (Mime->Headers) { Dest->Write(Mime->Headers, Header[1]); } if (Mime->DataStore) { char Buf[1024]; ssize_t Written = 0; ssize_t Read = 0; ssize_t r; while ((r = Mime->DataStore->Read(Buf, MIN(sizeof(Buf), Header[2]-Read) )) > 0) { ssize_t w; if ((w = Dest->Write(Buf, r)) <= 0) { // Write error break; } Written += w; Read += r; } // Check we've written out all the data LAssert(Written < Header[2]); if (Written < Header[2]) { return 0; } } for (unsigned i=0; iChildren.Length(); i++) { Mime->Children[i]->Binary.Write.Push(Dest, End); } return 1; } } return 0; } void LMime::LMimeBinary::LMimeWrite::Empty() { }