mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 14:16:07 -06:00
655 lines
15 KiB
C++
655 lines
15 KiB
C++
#include "ide.h"
|
|
|
|
struct UppHubNest : Moveable<UppHubNest> {
|
|
int tier = -1;
|
|
String name;
|
|
Vector<String> packages;
|
|
String description;
|
|
String repo;
|
|
String website;
|
|
String status = "unknown";
|
|
String category;
|
|
String list_name;
|
|
String readme;
|
|
String branch;
|
|
};
|
|
|
|
static Color s_Make(Color c) { return Blend(SColorPaper(), c, IsDarkTheme() ? 60 : 20); }
|
|
|
|
Color StatusPaper(const String& status)
|
|
{
|
|
static SColor broken([] { return s_Make(SLtRed()); });
|
|
static SColor experimental([] { return s_Make(SLtYellow()); });
|
|
static SColor stable([] { return s_Make(SLtGreen()); });
|
|
static SColor rolling([] { return s_Make(SLtCyan()); });
|
|
|
|
return decode(status, "broken", broken,
|
|
"experimental", experimental,
|
|
"stable", stable,
|
|
"rolling", rolling,
|
|
SColorPaper());
|
|
}
|
|
|
|
void VerifyUppHubRequirements()
|
|
{
|
|
if (HasGit())
|
|
{
|
|
return;
|
|
}
|
|
|
|
Loge() << UPP_FUNCTION_NAME << "(): Git is not available!";
|
|
ErrorOK(
|
|
"Git executable was not detected. UppHub will not work properly. Make sure git executable is present in your enviroment path. "
|
|
"You can find more information about requirements "
|
|
"[^https`:`/`/www`.ultimatepp`.org`/app`$ide`$UppHub`_en`-us`.html`#2^ here].&&"
|
|
"You can still use UppHub to view available packages but other operations will not work."
|
|
);
|
|
}
|
|
|
|
class UppHubSettingsDlg final : public WithUppHubSettingsLayout<TopWindow> {
|
|
public:
|
|
static constexpr auto GLOBAL_CONFIG_NAME = "UppHubDlgSettings";
|
|
|
|
public:
|
|
UppHubSettingsDlg();
|
|
|
|
void LoadGlobalSettings();
|
|
void SaveGlobalSettings();
|
|
|
|
private:
|
|
void RefreshCtrls();
|
|
};
|
|
|
|
UppHubSettingsDlg::UppHubSettingsDlg()
|
|
{
|
|
CtrlLayoutOKCancel(*this, "Settings");
|
|
FileSelectOpen(url, selfile);
|
|
seturl.WhenAction = [=] {
|
|
RefreshCtrls();
|
|
};
|
|
}
|
|
|
|
void UppHubSettingsDlg::LoadGlobalSettings()
|
|
{
|
|
LoadFromGlobal(*this, GLOBAL_CONFIG_NAME);
|
|
RefreshCtrls();
|
|
}
|
|
|
|
void UppHubSettingsDlg::SaveGlobalSettings()
|
|
{
|
|
StoreToGlobal(*this, GLOBAL_CONFIG_NAME);
|
|
}
|
|
|
|
void UppHubSettingsDlg::RefreshCtrls()
|
|
{
|
|
auto enable = static_cast<bool>(seturl.Get());
|
|
url.Enable(enable);
|
|
selfile.Enable(enable);
|
|
}
|
|
|
|
struct UppHubDlg : WithUppHubLayout<TopWindow> {
|
|
SplitterFrame splitter;
|
|
ArrayCtrl list;
|
|
RichTextView info;
|
|
|
|
VectorMap<String, UppHubNest> upv;
|
|
Index<String> loaded;
|
|
Progress pi;
|
|
bool loading_stopped;
|
|
String last_package;
|
|
VectorMap<String, String> readme;
|
|
|
|
// loading readme in background
|
|
TimeCallback delay;
|
|
String readme_url;
|
|
bool loading = false;
|
|
HttpRequest http;
|
|
|
|
UppHubSettingsDlg settings;
|
|
|
|
Value LoadJson(const String& url);
|
|
void Load(int tier, const String& url);
|
|
void Load();
|
|
void Install(bool noprompt = false);
|
|
void Uninstall(bool noprompt = false);
|
|
void Reinstall();
|
|
void Install(const Index<String>& ii);
|
|
void Update();
|
|
void Sync();
|
|
void SyncList();
|
|
void Settings();
|
|
bool Installed();
|
|
void UrlLoading();
|
|
void Menu(Bar& bar);
|
|
|
|
UppHubNest *Get(const String& name) { return upv.FindPtr(name); }
|
|
UppHubNest *Current() { return list.IsCursor() ? Get(list.Get("NAME")) : NULL; }
|
|
|
|
UppHubDlg();
|
|
|
|
bool Key(dword key, int count) override;
|
|
};
|
|
|
|
#define METHOD_NAME "UppHubDlg::" << UPP_FUNCTION_NAME << "(): "
|
|
|
|
UppHubDlg::UppHubDlg()
|
|
{
|
|
CtrlLayoutCancel(*this, "UppHub");
|
|
Sizeable().Zoomable();
|
|
|
|
parent.Add(list.SizePos());
|
|
parent.AddFrame(splitter.Right(info, 500));
|
|
|
|
list.AddKey("NAME");
|
|
list.AddColumn("Name").Sorting();
|
|
list.AddColumn("Category").Sorting();
|
|
list.AddColumn("Description");
|
|
|
|
list.ColumnWidths("109 80 338");
|
|
list.WhenSel = [=] {
|
|
UppHubNest *n = Current();
|
|
http.Abort();
|
|
http.Timeout(0);
|
|
http.New();
|
|
if(n && readme.Find(n->readme) < 0) {
|
|
readme_url = n->readme;
|
|
http.Url(readme_url);
|
|
loading = true;
|
|
delay.KillPost([=] { UrlLoading(); });
|
|
}
|
|
Sync();
|
|
};
|
|
list.WhenLeftDouble = [=] {
|
|
if(list.IsCursor()) {
|
|
if(Installed())
|
|
Reinstall();
|
|
else
|
|
Install();
|
|
}
|
|
};
|
|
list.WhenBar = [=](Bar& bar) {
|
|
if(list.IsCursor()) {
|
|
if(Installed()) {
|
|
bar.Add("Uninstall", [=] { Uninstall(); });
|
|
bar.Add("Reinstall", [=] { Reinstall(); });
|
|
}
|
|
else
|
|
bar.Add("Install", [=] { Install(); });
|
|
bar.Separator();
|
|
}
|
|
Menu(bar);
|
|
};
|
|
reinstall << [=] { Reinstall(); };
|
|
|
|
more << [=] {
|
|
MenuBar bar;
|
|
Menu(bar);
|
|
bar.Execute();
|
|
};
|
|
|
|
update << [=] { Update(); };
|
|
|
|
help << [=] { LaunchWebBrowser("https://www.ultimatepp.org/app$ide$UppHub_en-us.html"); };
|
|
|
|
search.NullText("Search (Ctrl+K)");
|
|
search.SetFilter([](int c) { return (int)ToUpper(ToAscii(c)); });
|
|
search << [=] { SyncList(); };
|
|
|
|
experimental <<= true;
|
|
broken <<= false;
|
|
|
|
category ^= experimental ^= broken ^= [=] { SyncList(); };
|
|
|
|
settings.LoadGlobalSettings();
|
|
}
|
|
|
|
INITBLOCK
|
|
{
|
|
RegisterGlobalConfig(UppHubSettingsDlg::GLOBAL_CONFIG_NAME);
|
|
}
|
|
|
|
bool UppHubDlg::Key(dword key, int count)
|
|
{
|
|
if(key == K_CTRL_K) {
|
|
search.SetFocus();
|
|
return true;
|
|
}
|
|
return TopWindow::Key(key, count);
|
|
}
|
|
|
|
void UppHubDlg::Menu(Bar& bar)
|
|
{
|
|
Ide *ide = (Ide *)TheIde();
|
|
String hubdir = GetHubDir();
|
|
bool sep = false;
|
|
UppHubNest *n = Current();
|
|
if(Installed()) {
|
|
String p = hubdir + "/" + n->name;
|
|
bar.Add("Open " + n->name + " Directory", [=] { ShellOpenFolder(p); });
|
|
bar.Add("Copy " + n->name + " Directory Path", [=] { WriteClipboardText(p); });
|
|
if(ide)
|
|
bar.Add("Terminal at " + n->name + " Directory", [=] { ide->LaunchTerminal(p); });
|
|
sep = true;
|
|
}
|
|
|
|
if(n && !n->website.IsEmpty()) {
|
|
bar.Add("Open " + n->name + " in Browser..", [=] { LaunchWebBrowser(n->website); });
|
|
sep = true;
|
|
}
|
|
|
|
if(sep)
|
|
bar.Separator();
|
|
|
|
bar.Add("Open UppHub Directory", [=] { ShellOpenFolder(hubdir); });
|
|
bar.Add("Copy UppHub Directory Path", [=] { WriteClipboardText(hubdir); });
|
|
if(ide)
|
|
bar.Add("Terminal at UppHub Directory", [=] { ide->LaunchTerminal(hubdir); });
|
|
bar.Separator();
|
|
bar.Add("Install everything..", [=] {
|
|
if(!PromptYesNo("Installing everything will take some time and will need a lot of storage space.&[/ Are you sure?"))
|
|
return;
|
|
Index<String> names;
|
|
for(const UppHubNest& n : upv)
|
|
if(!DirectoryExists(hubdir + "/" + n.name))
|
|
names.Add(n.name);
|
|
if(names.GetCount() == 0) {
|
|
Exclamation("No modules folders.");
|
|
return;
|
|
}
|
|
Install(names);
|
|
});
|
|
bar.Add("Reinstall all..", [=] {
|
|
Index<String> names;
|
|
for(const UppHubNest& n : upv)
|
|
if(DirectoryExists(hubdir + "/" + n.name))
|
|
names.Add(n.name);
|
|
if(names.GetCount() == 0) {
|
|
Exclamation("No modules folders.");
|
|
return;
|
|
}
|
|
if(!PromptYesNo("Reinstall all installed nests?&[/ (This will delete any local changes)]"))
|
|
return;
|
|
for(String n : names)
|
|
if(!DeleteFolderDeep(hubdir + "/" + n, true))
|
|
Exclamation("Failed to delete \1" + n);
|
|
Install(names);
|
|
});
|
|
bar.Add("Uninstall everything..", [=] {
|
|
if(!PromptYesNo("This will completely delete the local UppHub content.&Continue?"))
|
|
return;
|
|
for(FindFile ff(hubdir + "/*"); ff; ff++) {
|
|
String p = ff.GetPath();
|
|
if(ff.IsFolder()) {
|
|
if(!DeleteFolderDeep(p, true))
|
|
Exclamation("Failed to delete \1" + p);
|
|
}
|
|
else
|
|
if(ff.IsFile())
|
|
FileDelete(p);
|
|
}
|
|
SyncList();
|
|
});
|
|
bar.Separator();
|
|
bar.Add("Synchronize repos..", [=] {
|
|
Vector<String> dirs;
|
|
for(FindFile ff(hubdir + "/*"); ff; ff++) {
|
|
if(ff.IsFolder() && DirectoryExists(ff.GetPath() + "/.git"))
|
|
dirs.Add(ff.GetPath());
|
|
}
|
|
if(dirs.GetCount() == 0) {
|
|
Exclamation("No working folders.");
|
|
return;
|
|
}
|
|
RepoSyncDirs(dirs);
|
|
SyncList();
|
|
});
|
|
bar.Separator();
|
|
bar.Add("Set UppHub URL..", [=] { Settings(); });
|
|
}
|
|
|
|
bool UppHubDlg::Installed()
|
|
{
|
|
UppHubNest *n = Current();
|
|
return n && DirectoryExists(GetHubDir() + "/" + n->name);
|
|
}
|
|
|
|
void UppHubDlg::UrlLoading()
|
|
{
|
|
if(http.Do())
|
|
delay.KillPost([=] { UrlLoading(); });
|
|
else {
|
|
loading = false;
|
|
if(http.IsSuccess())
|
|
readme.GetAdd(readme_url) = http.GetContent();
|
|
Sync();
|
|
}
|
|
}
|
|
|
|
void UppHubDlg::Sync()
|
|
{
|
|
action.Disable();
|
|
reinstall.Disable();
|
|
if(list.IsCursor()) {
|
|
action.Enable();
|
|
if(Installed()) {
|
|
action.SetLabel("Uninstall");
|
|
reinstall.Enable();
|
|
action ^= [=] { Uninstall(); };
|
|
}
|
|
else {
|
|
action.SetLabel("Install");
|
|
action ^= [=] { Install(); };
|
|
}
|
|
}
|
|
UppHubNest *n = Current();
|
|
last_package = n && n->packages.GetCount() ? n->packages[0] : String();
|
|
if(!n) return;
|
|
String qtf;
|
|
Color c = StatusPaper(n->status);
|
|
qtf << "{{";
|
|
if(!IsDarkTheme())
|
|
qtf << "@(" << (int)c.GetR() << "." << (int)c.GetG() << "." << (int)c.GetB() << ")";
|
|
qtf << " Category: [* \1" << n->category << "\1], status: [* \1" << n->status << "\1], packages: [* \1" << Join(n->packages, " ") << "\1]";
|
|
if(Installed())
|
|
qtf << "&Status: [* installed]";
|
|
if (!n->website.IsEmpty())
|
|
qtf << "&&Website: [^" << n->website << "^ " << n->website << "]";
|
|
qtf << "}}&&";
|
|
String s = readme.Get(n->readme, String());
|
|
if(s.GetCount()) {
|
|
if(n->readme.EndsWith(".qtf"))
|
|
qtf << s;
|
|
else
|
|
if(n->readme.EndsWith(".md"))
|
|
qtf << MarkdownConverter().Tables().ToQtf(s);
|
|
else
|
|
qtf << "\1" << s;
|
|
}
|
|
else {
|
|
qtf << "[* \1" << n->description << "\1]";
|
|
qtf << (loading ? "&[/ Loading more information]" : "&[/ Failed to get ]\1" + n->readme);
|
|
}
|
|
info <<= qtf;
|
|
}
|
|
|
|
void UppHubDlg::Settings()
|
|
{
|
|
settings.LoadGlobalSettings();
|
|
if(settings.Execute() != IDOK) {
|
|
return;
|
|
}
|
|
|
|
settings.SaveGlobalSettings();
|
|
Load();
|
|
}
|
|
|
|
Value UppHubDlg::LoadJson(const String& url)
|
|
{
|
|
String s = LoadFile(url);
|
|
if(IsNull(s)) {
|
|
pi.SetText(url);
|
|
|
|
HttpRequest r(url);
|
|
r.WhenWait = r.WhenDo = [&] {
|
|
if(pi.StepCanceled()) {
|
|
r.Abort();
|
|
loading_stopped = true;
|
|
}
|
|
};
|
|
|
|
r.Execute();
|
|
if (!r.IsSuccess()) {
|
|
String msg = "Failed to execute UppHub download nests request with error code " + IntStr(r.GetStatusCode()) + ".";
|
|
Loge() << METHOD_NAME << msg;
|
|
return ErrorValue(msg);
|
|
}
|
|
|
|
if(loading_stopped) {
|
|
return ErrorValue();
|
|
}
|
|
|
|
s = r.GetContent();
|
|
}
|
|
|
|
Value v = ParseJSON(s);
|
|
if(v.IsError()) {
|
|
s.Replace(""", "\"");
|
|
s.Replace("&", "&");
|
|
v = ParseJSON(s);
|
|
if (v.IsError()) {
|
|
String msg = "Failed to parse Json file.";
|
|
Loge() << METHOD_NAME << msg;
|
|
return ErrorValue(msg);
|
|
}
|
|
}
|
|
return v;
|
|
}
|
|
|
|
void UppHubDlg::Load(int tier, const String& url)
|
|
{
|
|
if(loaded.Find(url) >= 0)
|
|
return;
|
|
loaded.Add(url);
|
|
|
|
Value v = LoadJson(url);
|
|
if (v.IsError()) {
|
|
auto error_text = GetErrorText(v);
|
|
if (!error_text.IsEmpty()) {
|
|
String msg = "Failed to load nests file with error \"" + GetErrorText(v) + "\".";
|
|
ErrorOK(msg);
|
|
}
|
|
return;
|
|
}
|
|
|
|
try {
|
|
String list_name = v["name"];
|
|
for(Value ns : v["nests"]) {
|
|
String url = ns["url"];
|
|
if(url.GetCount())
|
|
ns = LoadJson(url);
|
|
String name = ns["name"];
|
|
UppHubNest& n = upv.GetAdd(name);
|
|
n.name = name;
|
|
bool tt = tier > n.tier;
|
|
if(tt || n.packages.GetCount() == 0)
|
|
for(Value p : ns["packages"])
|
|
n.packages.Add(p);
|
|
auto Attr = [&](String& a, const char *id) {
|
|
if(tt || IsNull(a))
|
|
a = ns[id];
|
|
};
|
|
Attr(n.description, "description");
|
|
Attr(n.repo, "repository");
|
|
Attr(n.website, "website");
|
|
if(IsNull(n.website))
|
|
n.website = TrimRight(".git", n.repo);
|
|
Attr(n.category, "category");
|
|
Attr(n.status, "status");
|
|
Attr(n.readme, "readme");
|
|
Attr(n.branch, "branch");
|
|
|
|
n.list_name = list_name;
|
|
}
|
|
for(Value l : v["links"]) {
|
|
if(loading_stopped)
|
|
break;
|
|
Load(tier + 1, l);
|
|
}
|
|
}
|
|
catch(ValueTypeError) {}
|
|
}
|
|
|
|
void UppHubDlg::SyncList()
|
|
{
|
|
int sc = list.GetScroll();
|
|
Value k = list.GetKey();
|
|
list.Clear();
|
|
for(const UppHubNest& n : upv) {
|
|
String pkgs = Join(n.packages, " ");
|
|
auto AT = [&](const String& s) {
|
|
return AttrText(s).Bold(DirectoryExists(GetHubDir() + "/" + n.name)).NormalPaper(StatusPaper(n.status));
|
|
};
|
|
if(ToUpperAscii(n.name + n.category + n.description + pkgs).Find(~~search) >= 0 &&
|
|
(IsNull(category) || ~category == n.category) &&
|
|
(experimental || n.status != "experimental") &&
|
|
(broken || n.status != "broken"))
|
|
list.Add(n.name, AT(n.name), AT(n.category), AT(n.description), n.name);
|
|
}
|
|
|
|
list.DoColumnSort();
|
|
list.ScrollTo(sc);
|
|
if(!list.FindSetCursor(k))
|
|
list.GoBegin();
|
|
}
|
|
|
|
void UppHubDlg::Load()
|
|
{
|
|
loading_stopped = false;
|
|
loaded.Clear();
|
|
upv.Clear();
|
|
|
|
String url = Nvl(LoadFile(ConfigFile("upphub_root")),
|
|
(String)"https://raw.githubusercontent.com/ultimatepp/UppHub/main/nests.json");
|
|
|
|
if(settings.seturl)
|
|
url = ~settings.url;
|
|
|
|
Load(0, url);
|
|
|
|
category.ClearList();
|
|
Index<String> cat;
|
|
for(const UppHubNest& n : upv)
|
|
cat.FindAdd(n.category);
|
|
SortIndex(cat);
|
|
category.Add(Null, AttrText("All categories").Italic());
|
|
for(String s : cat)
|
|
category.Add(s);
|
|
category.GoBegin();
|
|
|
|
SyncList();
|
|
|
|
pi.Close();
|
|
}
|
|
|
|
void UppHubDlg::Update()
|
|
{
|
|
if(!PromptYesNo("Pull updates for all modules?"))
|
|
return;
|
|
UrepoConsole console;
|
|
for(const UppHubNest& n : upv) {
|
|
String dir = GetHubDir() + "/" + n.name;
|
|
if(DirectoryExists(dir))
|
|
console.Git(dir, "pull --rebase");
|
|
}
|
|
}
|
|
|
|
void UppHubDlg::Install(const Index<String>& ii_)
|
|
{
|
|
Index<String> ii = clone(ii_);
|
|
UrepoConsole console;
|
|
if(ii.GetCount()) {
|
|
int i = 0;
|
|
while(i < ii.GetCount()) {
|
|
String ns = ii[i++];
|
|
UppHubNest *n = Get(ns);
|
|
if(n) {
|
|
String dir = GetHubDir() + '/' + n->name;
|
|
if(!DirectoryExists(dir)) {
|
|
String cmd = "git clone --progress ";
|
|
if(n->branch.GetCount())
|
|
cmd << "-b " + n->branch << ' ';
|
|
cmd << n->repo;
|
|
cmd << " \"" << dir << "\"";
|
|
console.System(cmd);
|
|
for(String p : FindAllPaths(dir, "*.upp")) {
|
|
Package pkg;
|
|
pkg.Load(p);
|
|
for(const auto& u : pkg.uses)
|
|
for(const UppHubNest& n : upv)
|
|
for(const String& p : n.packages)
|
|
if(u.text == p) {
|
|
ii.FindAdd(n.name);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
console.Log("Done", Gray());
|
|
console.Perform();
|
|
InvalidatePackageCache();
|
|
}
|
|
ResetBlitz();
|
|
SyncList();
|
|
}
|
|
|
|
void UppHubDlg::Install(bool noprompt)
|
|
{
|
|
if(list.IsCursor() && (noprompt || PromptYesNo("Install " + ~list.GetKey() + "?")))
|
|
Install(Index<String>{ ~list.GetKey() });
|
|
}
|
|
|
|
void UppHubDlg::Uninstall(bool noprompt)
|
|
{
|
|
if(list.IsCursor() && (noprompt || PromptYesNo("Uninstall " + ~list.GetKey() + "?"))) {
|
|
if(!DeleteFolderDeep(GetHubDir() + "/" + ~list.GetKey(), true))
|
|
Exclamation("Failed to delete " + ~list.GetKey());
|
|
SyncList();
|
|
}
|
|
}
|
|
|
|
void UppHubDlg::Reinstall()
|
|
{
|
|
if(list.IsCursor() && PromptYesNo("Reinstall " + ~list.GetKey() + "?")) {
|
|
Uninstall(true);
|
|
Install(true);
|
|
}
|
|
}
|
|
|
|
String UppHub()
|
|
{
|
|
VerifyUppHubRequirements();
|
|
|
|
UppHubDlg dlg;
|
|
dlg.Load();
|
|
dlg.Run();
|
|
return dlg.last_package;
|
|
}
|
|
|
|
void UppHubAuto(const String& main)
|
|
{
|
|
bool noprompt = false;
|
|
Index<String> pmissing;
|
|
for(;;) {
|
|
Workspace wspc;
|
|
wspc.Scan(main);
|
|
Index<String> missing;
|
|
for(int i = 0; i < wspc.GetCount(); i++) {
|
|
String p = wspc[i];
|
|
if(!FileExists(PackagePath(p)))
|
|
missing.FindAdd(p);
|
|
}
|
|
|
|
if(missing.GetCount() == 0)
|
|
break;
|
|
|
|
UppHubDlg dlg;
|
|
dlg.Load();
|
|
Index<String> found;
|
|
for(const UppHubNest& n : dlg.upv)
|
|
for(const String& p : n.packages)
|
|
if(missing.Find(p) >= 0)
|
|
found.FindAdd(n.name);
|
|
|
|
if(found.GetCount() == missing.GetCount() && missing != pmissing &&
|
|
(noprompt || PromptYesNo("Missing packages were found in UppHub. Install?"))) {
|
|
dlg.Install(found);
|
|
noprompt = true;
|
|
pmissing = clone(missing);
|
|
continue;
|
|
}
|
|
|
|
PromptOK("Some packages are missing:&&[* \1" + Join(missing.GetKeys(), "\n"));
|
|
break;
|
|
}
|
|
}
|