From 3784bfac7788f0b4f968787caec29bada171432b Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 4 Feb 2021 19:30:52 -0500 Subject: [PATCH] Allow mod re-import / Save mod repo on import --- core/assets/bundles/bundle.properties | 3 +- core/src/mindustry/core/UI.java | 10 +++ core/src/mindustry/mod/ModListing.java | 1 + core/src/mindustry/mod/Mods.java | 85 +++++++++++++++---- core/src/mindustry/ui/dialogs/ModsDialog.java | 50 +++++++---- 5 files changed, 117 insertions(+), 32 deletions(-) diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index e44236ac92..4374c5ec08 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -44,7 +44,8 @@ be.check = Check for updates mod.featured.dialog.title = Mod Browser (WIP) mods.browser.selected = Selected mod mods.browser.add = Install -mods.github.open = View +mods.browser.reinstall = Reinstall +mods.github.open = Repo mods.browser.sortdate = Sort by recent mods.browser.sortstars = Sort by stars diff --git a/core/src/mindustry/core/UI.java b/core/src/mindustry/core/UI.java index e22bc6817f..34da281d03 100644 --- a/core/src/mindustry/core/UI.java +++ b/core/src/mindustry/core/UI.java @@ -363,6 +363,16 @@ public class UI implements ApplicationListener, Loadable{ }}.show(); } + public void showInfoOnHidden(String info, Runnable listener){ + new Dialog(""){{ + getCell(cont).growX(); + cont.margin(15).add(info).width(400f).wrap().get().setAlignment(Align.center, Align.center); + buttons.button("@ok", this::hide).size(110, 50).pad(4); + hidden(listener); + closeOnBack(); + }}.show(); + } + public void showStartupInfo(String info){ new Dialog(""){{ getCell(cont).growX(); diff --git a/core/src/mindustry/mod/ModListing.java b/core/src/mindustry/mod/ModListing.java index 08a1a8e963..a49ae981d2 100644 --- a/core/src/mindustry/mod/ModListing.java +++ b/core/src/mindustry/mod/ModListing.java @@ -4,6 +4,7 @@ package mindustry.mod; public class ModListing{ public String repo, name, author, lastUpdated, description, minGameVersion; public boolean hasScripts, hasJava; + public String[] contentTypes = {}; public int stars; @Override diff --git a/core/src/mindustry/mod/Mods.java b/core/src/mindustry/mod/Mods.java index 992e6672ee..01d17e847f 100644 --- a/core/src/mindustry/mod/Mods.java +++ b/core/src/mindustry/mod/Mods.java @@ -65,23 +65,39 @@ public class Mods implements Loadable{ }); } + /** @return the loaded mod found by name, or null if not found. */ + public @Nullable LoadedMod getMod(String name){ + return mods.find(m -> m.name.equals(name)); + } + /** @return the loaded mod found by class, or null if not found. */ public @Nullable LoadedMod getMod(Class type){ return mods.find(m -> m.enabled() && m.main != null && m.main.getClass() == type); } - /** Imports an external mod file.*/ - public void importMod(Fi file) throws IOException{ - Fi dest = modDirectory.child(file.name() + (file.extension().isEmpty() ? ".zip" : "")); - if(dest.exists()){ - throw new IOException("A file with the same name already exists in the mod folder!"); + /** Imports an external mod file. Folders are not supported here. */ + public LoadedMod importMod(Fi file) throws IOException{ + String baseName = file.nameWithoutExtension(); + String finalName = baseName; + //find a name to prevent any name conflicts + int count = 1; + while(modDirectory.child(finalName + ".zip").exists()){ + finalName = baseName + "" + count++; } + Fi dest = modDirectory.child(finalName + ".zip"); + file.copyTo(dest); try{ - mods.add(loadMod(dest)); + var loaded = loadMod(dest, true); + mods.add(loaded); requiresReload = true; sortMods(); + //try to load the mod's icon so it displays on import + Core.app.post(() -> { + loadIcon(loaded); + }); + return loaded; }catch(IOException e){ dest.delete(); throw e; @@ -113,13 +129,18 @@ public class Mods implements Loadable{ private void loadIcons(){ for(LoadedMod mod : mods){ - //try to load icon for each mod that can have one - if(mod.root.child("icon.png").exists()){ - try{ - mod.iconTexture = new Texture(mod.root.child("icon.png")); - }catch(Throwable t){ - Log.err("Failed to load icon for mod '" + mod.name + "'.", t); - } + loadIcon(mod); + } + } + + private void loadIcon(LoadedMod mod){ + //try to load icon for each mod that can have one + if(mod.root.child("icon.png").exists()){ + try{ + mod.iconTexture = new Texture(mod.root.child("icon.png")); + mod.iconTexture.setFilter(TextureFilter.linear); + }catch(Throwable t){ + Log.err("Failed to load icon for mod '" + mod.name + "'.", t); } } } @@ -586,8 +607,14 @@ public class Mods implements Loadable{ } /** Loads a mod file+meta, but does not add it to the list. - * Note that directories can be loaded as mods.*/ + * Note that directories can be loaded as mods. */ private LoadedMod loadMod(Fi sourceFile) throws Exception{ + return loadMod(sourceFile, false); + } + + /** Loads a mod file+meta, but does not add it to the list. + * Note that directories can be loaded as mods. */ + private LoadedMod loadMod(Fi sourceFile, boolean overwrite) throws Exception{ Time.mark(); ZipFi rootZip = null; @@ -615,8 +642,25 @@ public class Mods implements Loadable{ String mainClass = meta.main == null ? camelized.toLowerCase() + "." + camelized + "Mod" : meta.main; String baseName = meta.name.toLowerCase().replace(" ", "-"); - if(mods.contains(m -> m.name.equals(baseName))){ - throw new IllegalArgumentException("A mod with the name '" + baseName + "' is already imported."); + var other = mods.find(m -> m.name.equals(baseName)); + + if(other != null){ + if(overwrite && !other.hasSteamID()){ + //close zip file + if(other.root instanceof ZipFi){ + other.root.delete(); + } + //delete the old mod directory + if(other.file.isDirectory()){ + other.file.deleteDirectory(); + }else{ + other.file.delete(); + } + //unload + mods.remove(other); + }else{ + throw new IllegalArgumentException("A mod with the name '" + baseName + "' is already imported."); + } } Mod mainMod; @@ -701,6 +745,15 @@ public class Mods implements Loadable{ this.name = meta.name.toLowerCase().replace(" ", "-"); } + @Nullable + public String getRepo(){ + return Core.settings.getString("mod-" + name + "-repo", meta.repo); + } + + public void setRepo(String repo){ + Core.settings.put("mod-" + name + "-repo", repo); + } + public boolean enabled(){ return state == ModState.enabled || state == ModState.contentErrors; } diff --git a/core/src/mindustry/ui/dialogs/ModsDialog.java b/core/src/mindustry/ui/dialogs/ModsDialog.java index 6c0669582c..7969126a86 100644 --- a/core/src/mindustry/ui/dialogs/ModsDialog.java +++ b/core/src/mindustry/ui/dialogs/ModsDialog.java @@ -79,7 +79,9 @@ public class ModsDialog extends BaseDialog{ ui.showErrorMessage(Core.bundle.format("connectfail", status)); }else{ try{ - modList = new Json().fromJson(Seq.class, ModListing.class, strResult); + var j = new Json(); + j.setIgnoreUnknownFields(true); + modList = j.fromJson(Seq.class, ModListing.class, strResult); var d = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); Func parser = text -> { @@ -224,7 +226,9 @@ public class ModsDialog extends BaseDialog{ sel.clear(); sel.hide(); }); - sel.buttons.button("@mods.browser.add", Icon.download, () -> { + + var found = mods.list().find(l -> mod.repo != null && mod.repo.equals(l.getRepo())); + sel.buttons.button(found == null ? "@mods.browser.add" : "@mods.browser.reinstall", Icon.download, () -> { sel.hide(); githubImportMod(mod.repo); }); @@ -349,7 +353,7 @@ public class ModsDialog extends BaseDialog{ } private void reload(){ - ui.showInfo("@mods.reloadexit", () -> Core.app.exit()); + ui.showInfoOnHidden("@mods.reloadexit", () -> Core.app.exit()); } private void showMod(LoadedMod mod){ @@ -361,6 +365,10 @@ public class ModsDialog extends BaseDialog{ dialog.buttons.button("@mods.openfolder", Icon.link, () -> Core.app.openFolder(mod.file.absolutePath())); } + if(mod.getRepo() != null){ + dialog.buttons.button("@mods.github.open", Icon.link, () -> Core.app.openURI("https://github.com/" + mod.getRepo())); + } + //TODO improve this menu later dialog.cont.pane(desc -> { desc.center(); @@ -406,8 +414,6 @@ public class ModsDialog extends BaseDialog{ } } - - dialog.show(); } @@ -415,7 +421,8 @@ public class ModsDialog extends BaseDialog{ try{ Fi file = tmpDirectory.child(repo.replace("/", "") + ".zip"); Streams.copy(result.getResultAsStream(), file.write(false)); - mods.importMod(file); + var mod = mods.importMod(file); + mod.setRepo(repo); file.delete(); Core.app.post(() -> { try{ @@ -431,15 +438,28 @@ public class ModsDialog extends BaseDialog{ } private void githubImportMod(String name){ - //try several branches - //TODO use only the main branch as specified in meta - githubImportBranch("6.0", name, e1 -> { - githubImportBranch("master", name, e2 -> { - githubImportBranch("main", name, e3 -> { - ui.showErrorMessage(Core.bundle.format("connectfail", e2)); - ui.loadfrag.hide(); - }); - }); + Core.net.httpGet("https://api.github.com/repos/" + name, res -> { + if(checkError(res)){ + String mainBranch = Jval.read(res.getResultAsString()).getString("default_branch"); + + githubImportBranch(mainBranch, name, this::showStatus); + } + }, t -> Core.app.post(() -> modError(t))); + } + + private boolean checkError(HttpResponse res){ + if(res.getStatus() == HttpStatus.OK){ + return true; + }else{ + showStatus(res.getStatus()); + return false; + } + } + + private void showStatus(HttpStatus status){ + Core.app.post(() -> { + ui.showErrorMessage(Core.bundle.format("connectfail", Strings.capitalize(status.toString().toLowerCase()))); + ui.loadfrag.hide(); }); }