Allow mod re-import / Save mod repo on import

This commit is contained in:
Anuken
2021-02-04 19:30:52 -05:00
parent 54754cd177
commit 3784bfac77
5 changed files with 117 additions and 32 deletions

View File

@@ -44,7 +44,8 @@ be.check = Check for updates
mod.featured.dialog.title = Mod Browser (WIP) mod.featured.dialog.title = Mod Browser (WIP)
mods.browser.selected = Selected mod mods.browser.selected = Selected mod
mods.browser.add = Install mods.browser.add = Install
mods.github.open = View mods.browser.reinstall = Reinstall
mods.github.open = Repo
mods.browser.sortdate = Sort by recent mods.browser.sortdate = Sort by recent
mods.browser.sortstars = Sort by stars mods.browser.sortstars = Sort by stars

View File

@@ -363,6 +363,16 @@ public class UI implements ApplicationListener, Loadable{
}}.show(); }}.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){ public void showStartupInfo(String info){
new Dialog(""){{ new Dialog(""){{
getCell(cont).growX(); getCell(cont).growX();

View File

@@ -4,6 +4,7 @@ package mindustry.mod;
public class ModListing{ public class ModListing{
public String repo, name, author, lastUpdated, description, minGameVersion; public String repo, name, author, lastUpdated, description, minGameVersion;
public boolean hasScripts, hasJava; public boolean hasScripts, hasJava;
public String[] contentTypes = {};
public int stars; public int stars;
@Override @Override

View File

@@ -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. */ /** @return the loaded mod found by class, or null if not found. */
public @Nullable LoadedMod getMod(Class<? extends Mod> type){ public @Nullable LoadedMod getMod(Class<? extends Mod> type){
return mods.find(m -> m.enabled() && m.main != null && m.main.getClass() == type); return mods.find(m -> m.enabled() && m.main != null && m.main.getClass() == type);
} }
/** Imports an external mod file.*/ /** Imports an external mod file. Folders are not supported here. */
public void importMod(Fi file) throws IOException{ public LoadedMod importMod(Fi file) throws IOException{
Fi dest = modDirectory.child(file.name() + (file.extension().isEmpty() ? ".zip" : "")); String baseName = file.nameWithoutExtension();
if(dest.exists()){ String finalName = baseName;
throw new IOException("A file with the same name already exists in the mod folder!"); //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); file.copyTo(dest);
try{ try{
mods.add(loadMod(dest)); var loaded = loadMod(dest, true);
mods.add(loaded);
requiresReload = true; requiresReload = true;
sortMods(); sortMods();
//try to load the mod's icon so it displays on import
Core.app.post(() -> {
loadIcon(loaded);
});
return loaded;
}catch(IOException e){ }catch(IOException e){
dest.delete(); dest.delete();
throw e; throw e;
@@ -113,13 +129,18 @@ public class Mods implements Loadable{
private void loadIcons(){ private void loadIcons(){
for(LoadedMod mod : mods){ for(LoadedMod mod : mods){
//try to load icon for each mod that can have one loadIcon(mod);
if(mod.root.child("icon.png").exists()){ }
try{ }
mod.iconTexture = new Texture(mod.root.child("icon.png"));
}catch(Throwable t){ private void loadIcon(LoadedMod mod){
Log.err("Failed to load icon for mod '" + mod.name + "'.", t); //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. /** 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{ 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(); Time.mark();
ZipFi rootZip = null; ZipFi rootZip = null;
@@ -615,8 +642,25 @@ public class Mods implements Loadable{
String mainClass = meta.main == null ? camelized.toLowerCase() + "." + camelized + "Mod" : meta.main; String mainClass = meta.main == null ? camelized.toLowerCase() + "." + camelized + "Mod" : meta.main;
String baseName = meta.name.toLowerCase().replace(" ", "-"); String baseName = meta.name.toLowerCase().replace(" ", "-");
if(mods.contains(m -> m.name.equals(baseName))){ var other = mods.find(m -> m.name.equals(baseName));
throw new IllegalArgumentException("A mod with the name '" + baseName + "' is already imported.");
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; Mod mainMod;
@@ -701,6 +745,15 @@ public class Mods implements Loadable{
this.name = meta.name.toLowerCase().replace(" ", "-"); 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(){ public boolean enabled(){
return state == ModState.enabled || state == ModState.contentErrors; return state == ModState.enabled || state == ModState.contentErrors;
} }

View File

@@ -79,7 +79,9 @@ public class ModsDialog extends BaseDialog{
ui.showErrorMessage(Core.bundle.format("connectfail", status)); ui.showErrorMessage(Core.bundle.format("connectfail", status));
}else{ }else{
try{ 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'"); var d = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
Func<String, Date> parser = text -> { Func<String, Date> parser = text -> {
@@ -224,7 +226,9 @@ public class ModsDialog extends BaseDialog{
sel.clear(); sel.clear();
sel.hide(); 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(); sel.hide();
githubImportMod(mod.repo); githubImportMod(mod.repo);
}); });
@@ -349,7 +353,7 @@ public class ModsDialog extends BaseDialog{
} }
private void reload(){ private void reload(){
ui.showInfo("@mods.reloadexit", () -> Core.app.exit()); ui.showInfoOnHidden("@mods.reloadexit", () -> Core.app.exit());
} }
private void showMod(LoadedMod mod){ 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())); 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 //TODO improve this menu later
dialog.cont.pane(desc -> { dialog.cont.pane(desc -> {
desc.center(); desc.center();
@@ -406,8 +414,6 @@ public class ModsDialog extends BaseDialog{
} }
} }
dialog.show(); dialog.show();
} }
@@ -415,7 +421,8 @@ public class ModsDialog extends BaseDialog{
try{ try{
Fi file = tmpDirectory.child(repo.replace("/", "") + ".zip"); Fi file = tmpDirectory.child(repo.replace("/", "") + ".zip");
Streams.copy(result.getResultAsStream(), file.write(false)); Streams.copy(result.getResultAsStream(), file.write(false));
mods.importMod(file); var mod = mods.importMod(file);
mod.setRepo(repo);
file.delete(); file.delete();
Core.app.post(() -> { Core.app.post(() -> {
try{ try{
@@ -431,15 +438,28 @@ public class ModsDialog extends BaseDialog{
} }
private void githubImportMod(String name){ private void githubImportMod(String name){
//try several branches Core.net.httpGet("https://api.github.com/repos/" + name, res -> {
//TODO use only the main branch as specified in meta if(checkError(res)){
githubImportBranch("6.0", name, e1 -> { String mainBranch = Jval.read(res.getResultAsString()).getString("default_branch");
githubImportBranch("master", name, e2 -> {
githubImportBranch("main", name, e3 -> { githubImportBranch(mainBranch, name, this::showStatus);
ui.showErrorMessage(Core.bundle.format("connectfail", e2)); }
ui.loadfrag.hide(); }, 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();
}); });
} }