diff --git a/Lvc/src/DropDownBtn.cpp b/Lvc/src/DropDownBtn.cpp --- a/Lvc/src/DropDownBtn.cpp +++ b/Lvc/src/DropDownBtn.cpp @@ -1,144 +1,145 @@ #include "lgi/common/Lgi.h" #include "Lvc.h" struct DropDownBtnPriv { DropDownBtn *View; int EditId; DropDownBtnPriv(DropDownBtn *view) { View = view; EditId = -1; } LViewI *GetEdit() { LViewI *e = NULL; if (View && View->GetParent()) View->GetParent()->GetViewById(EditId, e); LAssert(e != 0); return e; } }; class DropLst : public LPopup { public: DropDownBtnPriv *d; LList *Lst; DropLst(DropDownBtnPriv *priv, LView *owner) : Lst(NULL), LPopup(owner) { d = priv; int x = 300; LRect r(0, 0, x, 200); SetPos(r); AddView(Lst = new LList(10)); Lst->Sunken(false); Lst->ShowColumnHeader(false); Lst->AddColumn("", x); } void OnPosChange() { LRect c = GetClient(); c.Inset(1, 1); if (Lst) Lst->SetPos(c); } void OnPaint(LSurface *pDC) { pDC->Colour(LColour::Black); pDC->Box(); } int OnNotify(LViewI *c, LNotification n) { if (c->GetId() == 10 && n.Type == LNotifyItemClick) { LListItem *i = Lst->GetSelected(); LViewI *e = d->GetEdit(); if (e && i) { e->Name(i->GetText(0)); } Visible(false); } return 0; } }; DropDownBtn::DropDownBtn() : LDropDown(-1, 0, 0, 100, 24, NULL), ResObject(Res_Custom) { d = new DropDownBtnPriv(this); d->EditId = -1; SetPopup(Pu = new DropLst(d, this)); } DropDownBtn::~DropDownBtn() { DeleteObj(d); } LString::Array DropDownBtn::GetList() { LString::Array a; if (Pu && Pu->Lst) { for (auto i: *Pu->Lst) { a.New() = i->GetText(0); } } return a; } bool DropDownBtn::OnLayout(LViewLayoutInfo &Inf) { if (Inf.Width.Min) { // Vertical layout Inf.Height.Min = Inf.Height.Max = 23; } else { // Horizontal layout Inf.Width.Min = Inf.Width.Max = 23; } return true; } bool DropDownBtn::SetList(int EditCtrl, LString::Array a) { if (!Pu || !Pu->Lst) return false; Pu->Lst->Empty(); d->EditId = EditCtrl; for (auto s: a) { LListItem *i = new LListItem; i->SetText(s); - Pu->Lst->Insert(i); + Pu->Lst->Insert(i, -1, false); } + Pu->Lst->UpdateAllItems(); return true; } class DropDownBtnFactory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (!stricmp(Class, "DropDownBtn")) return new DropDownBtn; return NULL; } } DropDownBtnFactoryInst; \ No newline at end of file diff --git a/Lvc/src/SshConnection.cpp b/Lvc/src/SshConnection.cpp --- a/Lvc/src/SshConnection.cpp +++ b/Lvc/src/SshConnection.cpp @@ -1,474 +1,480 @@ #include "lgi/common/Lgi.h" #include "Lvc.h" #include "SshConnection.h" #define TIMEOUT_PROMPT 1000 #define PROFILE_WaitPrompt 0 #define PROFILE_OnEvent 0 #define DEBUG_SSH_LOGGING 0 #if DEBUG_SSH_LOGGING #define SSH_LOG(...) d->sLog.Log(__VA_ARGS__) #else #define SSH_LOG(...) #endif ////////////////////////////////////////////////////////////////// SshConnection::SshConnection(LTextLog *log, const char *uri, const char *prompt) : LSsh(log), LEventTargetThread("SshConnection") { auto Wnd = log->GetWindow(); GuiHnd = Wnd->AddDispatch(); Prompt = prompt; Host.Set(Uri = uri); d = NULL; LScriptArguments Args(NULL); if (Wnd->CallMethod(METHOD_GetContext, Args)) { if (Args.GetReturn()->Type == GV_VOID_PTR) d = (AppPriv*) Args.GetReturn()->Value.Ptr; } } bool SshConnection::DetectVcs(VcFolder *Fld) { LAutoPtr p(new LString(Fld->GetUri().sPath)); TypeNotify.Add(Fld); return PostObject(GetHandle(), M_DETECT_VCS, p); } bool SshConnection::Command(VcFolder *Fld, LString Exe, LString Args, ParseFn Parser, ParseParams *Params) { if (!Fld || !Exe || !Parser) return false; LAutoPtr p(new SshParams(this)); p->f = Fld; p->Exe = Exe; p->Args = Args; p->Parser = Parser; p->Params = Params; p->Path = Fld->GetUri().sPath; return PostObject(GetHandle(), M_RUN_CMD, p); } LStream *SshConnection::GetConsole() { if (!Connected) { auto r = Open(Host.sHost, Host.sUser, Host.sPass, true); Log->Print("Ssh: %s open: %i\n", Host.sHost.Get(), r); } if (Connected && !c) { c = CreateConsole(); WaitPrompt(c); } return c; } class ProgressListItem : public LListItem { int64_t v, maximum; public: ProgressListItem(int64_t mx = 100) : maximum(mx) { v = 0; } + ~ProgressListItem() + { + int asd=0; + } + int64_t Value() { return v; } void Value(int64_t val) { v = val; Update(); } void OnPaint(LItem::ItemPaintCtx &Ctx) { auto pDC = Ctx.pDC; pDC->Colour(Ctx.Back); pDC->Rectangle(&Ctx); auto Fnt = GetList()->GetFont(); LDisplayString ds(Fnt, LFormatSize(v)); Fnt->Transparent(true); Fnt->Colour(Ctx.Fore, Ctx.Back); ds.Draw(pDC, Ctx.x1 + 10, Ctx.y1 + ((Ctx.Y() - ds.Y()) >> 1)); pDC->Colour(LProgressView::cNormal); int x1 = 120; int prog = Ctx.X() - x1; int x2 = (int) (v * prog / maximum); pDC->Rectangle(Ctx.x1 + x1, Ctx.y1 + 1, Ctx.x1 + x1 + x2, Ctx.y2 - 1); } }; #if PROFILE_WaitPrompt #define PROFILE(name) prof.Add(name) #else #define PROFILE(name) #endif LString LastLine(LString &input) { #define Ws(ch) ( ((ch) == '\r') || ((ch) == '\n') || ((ch) == '\b') ) char *e = input.Get() + input.Length(); while (e > input.Get() && Ws(e[-1])) e--; char *s = e; while (s > input.Get() && !Ws(s[-1])) s--; return LString(s, e - s); } LString LastLine(LStringPipe &input) { #define Ws(ch) ( ((ch) == '\r') || ((ch) == '\n') || ((ch) == '\b') ) LString s, ln; input.Iterate([&s, &ln](auto ptr, auto bytes) { s = LString((char*)ptr, bytes) + s; auto end = s.Get() + s.Length(); for (auto p = end - 1; p >= s.Get(); p--) { if (Ws(*p)) { while (p < end && (*p == 0 || Ws(*p))) p++; ln = p; break; } } return ln.Get() == NULL; }, true); // if (!ln.Get()) // ln = s; LAssert(ln.Find("\n") < 0); DeEscape(ln); return ln; } bool SshConnection::WaitPrompt(LStream *con, LString *Data, const char *Debug) { LStringPipe out(4 << 10); auto Ts = LCurrentTime(); auto LastReadTs = Ts; ProgressListItem *Prog = NULL; #if PROFILE_WaitPrompt LProfile prof("WaitPrompt", 100); #endif size_t BytesRead = 0; bool CheckLast = true; while (!LSsh::Cancel->IsCancelled()) { PROFILE("read"); auto buf = out.GetBuffer(); if (!buf.ptr) { LAssert(!"Alloc failed."); LgiTrace("WaitPrompt.%s alloc failed.\n", Debug); return false; } auto rd = con->Read(buf.ptr, buf.len); if (rd < 0) { // Error case if (Debug) LgiTrace("WaitPrompt.%s rd=%i\n", Debug, rd); return false; } if (rd > 0) { // Got some data... keep asking for more: LString tmp((char*)buf.ptr, rd); SSH_LOG("waitPrompt data:", rd, tmp); BytesRead += rd; buf.Commit(rd); CheckLast = true; LastReadTs = LCurrentTime(); continue; } if (LCurrentTime() - LastReadTs > 4000) { auto sz = out.GetSize(); SSH_LOG("waitPrompt out:", sz, out); auto last = LastLine(out); // Does the buffer end with a ':' on a line by itself? // Various version control CLI's do that to pageinate data. // Obviously we're not going to deal with that directly, // but the developer will need to know that's happened. if (out.GetSize() > 2) { auto last = LastLine(out); if (last == ":") return false; } } if (!CheckLast) { // We've already checked the buffer for the prompt... LSleep(10); // Don't use too much CPU continue; } PROFILE("LastLine"); CheckLast = false; auto last = LastLine(out); // LgiTrace("last='%s'\n", last.Get()); PROFILE("matchstr"); auto result = MatchStr(Prompt, last); SSH_LOG("waitPrompt result:", result, Prompt, last); if (Debug) { LgiTrace("WaitPrompt.%s match='%s' with '%s' = %i\n", Debug, Prompt.Get(), last.Get(), result); } if (result) { if (Data) { PROFILE("data process"); auto response = out.NewLStr(); if (response) { DeEscape(response); // Strip first line off the start.. it's the command... // And the last line... it's the prompt auto start = response.Get(); auto end = response.Get() + response.Length(); while (start < end && *start != '\n') start++; while (start < end && (*start == '\n' || *start == 0)) start++; while (end > start && end[-1] != '\n') end--; Data->Set(start, end - start); } SSH_LOG("waitPrompt data:", *Data); } if (Debug) LgiTrace("WaitPrompt.%s Prompt data=%i\n", Debug, Data?(int)Data->Length():0); break; } auto Now = LCurrentTime(); if (Now - Ts >= TIMEOUT_PROMPT) { if (!Prog && d->Commits) { Prog = new ProgressListItem(1 << 20); d->Commits->Insert(Prog); } if (Prog) Prog->Value(out.GetSize()); Log->Print("...reading: %s\n", LFormatSize(BytesRead).Get()); BytesRead = 0; Ts = Now; } } - DeleteObj(Prog); + if (d->Commits->HasItem(Prog)) + DeleteObj(Prog); // Something else may delete it before we have a chance to. return true; } bool SshConnection::HandleMsg(LMessage *m) { if (m->Msg() != M_RESPONSE) return false; LAutoPtr u((SshParams*)m->A()); if (!u || !u->c) return false; SshConnection &c = *u->c; AppPriv *d = c.d; if (!d) return false; if (u->Vcs) // Check the VCS type.. { c.Types.Add(u->Path, u->Vcs); for (auto f: c.TypeNotify) { if (d->Tree->HasItem(f)) f->OnVcsType(u->Output); else LgiTrace("%s:%i - Folder no longer in tree (recently deleted?).\n", _FL); } } else { if (d && d->Tree->HasItem(u->f)) u->f->OnSshCmd(u); else LgiTrace("%s:%i - Folder no longer in tree (recently deleted?).\n", _FL); } return true; } LString PathFilter(LString s) { auto parts = s.SplitDelimit("/"); if ( (parts[0].Equals("~") || parts[0].Equals(".")) && s(0) == '/' ) { return s(1, -1).Replace(" ", "\\ "); } return s.Replace(" ", "\\ "); } #if PROFILE_OnEvent #define PROF(name) prof.Add(name) #else #define PROF(name) #endif LMessage::Result SshConnection::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_DETECT_VCS: { LAutoPtr p; if (!ReceiveA(p, Msg)) { LAssert(!"Incorrect param."); break; } LAutoPtr r(new SshParams(this)); LString ls, out; LString::Array lines; VersionCtrl Vcs = VcNone; LString path = PathFilter(*p); LStream *con = GetConsole(); if (!con) { r->Output = "Error: Failed to get console."; r->Vcs = VcError; } else { ls.Printf("find %s -maxdepth 1 -printf \"%%f\n\"\n", path.Get()); SSH_LOG("detectVcs:", ls); con->Write(ls, ls.Length()); auto pr = WaitPrompt(con, &out); lines = out.SplitDelimit("\r\n"); for (auto ln: lines) { if (ln.Equals(".svn")) Vcs = VcSvn; else if (ln.Equals("CVS")) Vcs = VcCvs; else if (ln.Equals(".hg")) Vcs = VcHg; else if (ln.Equals(".git")) Vcs = VcGit; } } r->Path = *p; printf("r->Output=%s\n", r->Output.Get()); if (Vcs == VcError) ; else if (Vcs != VcNone) { r->Vcs = Vcs; r->ExitCode = 0; } else { r->Vcs = VcError; r->Output.Printf("Error: no VCS detected.\n%s\n%s", ls.Get(), lines.Length() ? lines.Last().Get() : "#nodata"); } PostObject(GuiHnd, M_RESPONSE, r); break; } case M_RUN_CMD: { #if PROFILE_OnEvent LProfile prof("OnEvent"); #endif LAutoPtr p; if (!ReceiveA(p, Msg)) break; PROF("get console"); LString path = PathFilter(p->Path); LStream *con = GetConsole(); if (!con) break; auto Debug = p->Params && p->Params->Debug; PROF("cd"); LString cmd; cmd.Printf("cd %s\n", path.Get()); SSH_LOG(">>>> cd:", path); auto wr = con->Write(cmd, cmd.Length()); PROF("cd wait"); auto pr = WaitPrompt(con, NULL, Debug?"Cd":NULL); PROF("cmd"); cmd.Printf("%s %s\n", p->Exe.Get(), p->Args.Get()); SSH_LOG(">>>> cmd:", cmd); if (Log) Log->Print("%s", cmd.Get()); wr = con->Write(cmd, cmd.Length()); PROF("cmd wait"); pr = WaitPrompt(con, &p->Output, Debug?"Cmd":NULL); PROF("result"); LString result; cmd = "echo $?\n"; SSH_LOG(">>>> result:", cmd); wr = con->Write(cmd, cmd.Length()); PROF("result wait"); pr = WaitPrompt(con, &result, Debug?"Echo":NULL); if (pr) { p->ExitCode = (int)result.Int(); if (Log) Log->Print("... result=%i\n", p->ExitCode); } else if (Log) Log->Print("... result=failed\n"); PostObject(GuiHnd, M_RESPONSE, p); break; } default: { LAssert(!"Unhandled msg."); break; } } return 0; } diff --git a/include/lgi/common/StructuredIo.h b/include/lgi/common/StructuredIo.h --- a/include/lgi/common/StructuredIo.h +++ b/include/lgi/common/StructuredIo.h @@ -1,335 +1,335 @@ #ifndef _STRUCTURED_IO_H_ #define _STRUCTURED_IO_H_ #include #include "lgi/common/Variant.h" #include "lgi/common/LMallocArray.h" #define DEBUG_STRUCT_IO 0 /* Generic base data field: (where size_t == EncSize/DecSize field) uint8_t type; // LVariantType size_t name_len; char name[name_len]; size_t data_size; uint8_t data[data_size]; Objects are wrapped with generic fields of the type GV_CUSTOM // start of object... ...array of member fields.... GV_VOID_PTR // end of object */ class LStructuredIo : public LMallocArray { bool Write = true; size_t Pos = 0; protected: bool CheckSpace(size_t bytes) { auto l = Pos + bytes; if (l > Length()) { if (!Length((l + 255) & ~0xff)) return false; LAssert(Length() >= l); } return true; } constexpr static int SevenMask = 0x7f; template void EncSize(LPointer &p, T sz) { if (!sz) *p.u8++ = 0; else while (sz) { uint8_t bits = sz & SevenMask; sz >>= 7; *p.u8++ = bits | (sz ? 0x80 : 0); } } template void DecSize(LPointer &p, T &sz) { sz = 0; uint8_t bits, shift = 0; do { bits = *p.u8++; sz |= ((T)bits & SevenMask) << shift; shift += 7; } while (bits & 0x80); } bool Encode(uint8_t type, const void *obj = NULL, size_t sz = 0, const char *name = NULL) { LPointer p; LAssert(Write); auto name_len = Strlen(name); if (!CheckSpace(1 + 8 + name_len + 8 + sz)) return false; p.u8 = AddressOf(Pos); #if DEBUG_STRUCT_IO auto type_addr = p.u8 - AddressOf(); #endif *p.u8++ = type; EncSize(p, name_len); if (name) { memcpy(p.u8, name, name_len); p.u8 += name_len; } EncSize(p, sz); #if DEBUG_STRUCT_IO auto data_addr = p.u8 - AddressOf(); #endif if (obj) { memcpy(p.u8, obj, sz); p.u8 += sz; } else LAssert(sz == 0); Pos = p.u8 - AddressOf(); #if DEBUG_STRUCT_IO LgiTrace("Encode(%i @ %i,%i sz=%i) after=%i\n", type, (int)type_addr, (int)data_addr, (int)sz, (int)Pos); #endif return true; } public: constexpr static LVariantType StartObject = GV_CUSTOM; constexpr static LVariantType EndObject = GV_VOID_PTR; constexpr static LVariantType EndRow = GV_NULL; LStructuredIo(bool write) : Write(write) { } bool GetWrite() { return Write; } size_t GetPos() { return Pos; } void Bool(bool &b, const char *name = NULL) { Encode(GV_BOOL, &b, sizeof(b), name); } template void Int(T &i, const char *name = NULL) { Encode(GV_INT64, &i, sizeof(i), name); } template void Float(T &f, const char *name = NULL) { Encode(GV_DOUBLE, &f, sizeof(f), name); } template void String(T *str, ssize_t sz = -1, const char *name = NULL) { if (sz < 0) sz = Strlen(str); Encode(sizeof(*str) > 1 ? GV_WSTRING : GV_STRING, str, sz * sizeof(T), name); } void String(LString &str, const char *name = NULL) { Encode(GV_STRING, str.Get(), str.Length(), name); } template void Binary(T *data, size_t elements, const char *name = NULL) { if (!data || elements == 0) return; Encode(GV_BINARY, data, sizeof(*data)*elements, name); } struct ObjRef { LStructuredIo *io; ObjRef(ObjRef &&r) : io(NULL) { LSwap(io, r.io); } ObjRef(LStructuredIo *parent) : io(parent) { } ~ObjRef() { if (io) io->Encode(EndObject); } }; ObjRef StartObj(const char *name) { ObjRef r(this); Encode(StartObject, NULL, 0, name); return r; } bool Decode(std::function callback, Progress *prog = NULL) { if (Length() == 0) return false; LPointer p; auto end = AddressOf()+Length(); p.u8 = AddressOf(Pos); #define CHECK_EOF(sz) if (p.u8 + sz > end) return false CHECK_EOF(1); #if DEBUG_STRUCT_IO auto type_addr = p.u8 - AddressOf(); #endif LVariantType type = (LVariantType)(*p.u8++); if (type >= GV_MAX) return false; if (type == EndRow) { callback(type, 0, NULL, NULL); Pos = p.u8 - AddressOf(); return true; } size_t name_len, data_size; DecSize(p, name_len); CHECK_EOF(name_len); LString name(p.c, name_len); p.s8 += name_len; DecSize(p, data_size); CHECK_EOF(data_size); #if DEBUG_STRUCT_IO auto data_addr = p.u8 - AddressOf(); #endif callback(type, data_size, p.u8, name); p.u8 += data_size; Pos = p.u8 - AddressOf(); #if DEBUG_STRUCT_IO LgiTrace("Decode(%i @ %i,%i sz=%i) after=%i\n", type, (int)type_addr, (int)data_addr, (int)data_size, (int)Pos); #endif return true; } bool Flush(LStream *s) { - if (!s || !Write) + if (!s || !Write || Length() == 0) return false; (*this)[Pos++] = EndRow; bool Status = s->Write(AddressOf(), Pos) == Pos; Pos = 0; return Status; } }; #define IntIo(type) inline void StructIo(LStructuredIo &io, type i) { io.Int(i); } #define StrIo(type) inline void StructIo(LStructuredIo &io, type i) { io.String(i); } IntIo(char) IntIo(unsigned char) IntIo(short) IntIo(unsigned short) IntIo(int) IntIo(unsigned int) IntIo(int64_t) IntIo(uint64_t) StrIo(char*); StrIo(const char*); StrIo(wchar_t*); StrIo(const wchar_t*); inline void StructIo(LStructuredIo &io, LString &s) { if (io.GetWrite()) io.String(s.Get(), s.Length()); else io.Decode([&s](auto type, auto sz, auto ptr, auto name) { if (type == GV_STRING && ptr && sz > 0) s.Set((char*)ptr, sz); }); } inline void StructIo(LStructuredIo &io, LStringPipe &p) { // auto obj = io.StartObj("LStringPipe"); if (io.GetWrite()) { p.Iterate([&io](auto ptr, auto bytes) { io.String(ptr, bytes); return true; }); } else { io.Decode([&p](auto type, auto sz, auto ptr, auto name) { if (type == GV_STRING && ptr && sz > 0) p.Write(ptr, sz); }); } } inline void StructIo(LStructuredIo &io, LRect &r) { auto obj = io.StartObj("LRect"); io.Int(r.x1, "x1"); io.Int(r.y1, "y1"); io.Int(r.x2, "x2"); io.Int(r.y2, "y2"); } inline void StructIo(LStructuredIo &io, LColour &c) { auto obj = io.StartObj("LColour"); LString cs; uint8_t r, g, b, a; if (io.GetWrite()) { cs = LColourSpaceToString(c.GetColourSpace()); r = c.r(); g = c.g(); b = c.b(); a = c.a(); } io.String(cs, "colourSpace"); io.Int(r, "r"); io.Int(g, "g"); io.Int(b, "b"); io.Int(a, "a"); if (!io.GetWrite()) { c.SetColourSpace(LStringToColourSpace(cs)); c.r(r); c.g(g); c.b(b); c.a(a); } } #endif \ No newline at end of file