Skip to content

Commit d06654d

Browse files
committed
add a way to download a dependency
1 parent 0fbdf27 commit d06654d

File tree

3 files changed

+147
-21
lines changed

3 files changed

+147
-21
lines changed

polymod/Polymod.hx

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -817,12 +817,17 @@ typedef ModContributor =
817817
};
818818

819819
/**
820-
* A type representing a mod's dependencies.
821-
*
822-
* The map takes the mod's ID as the key and the required version as the value.
823-
* The version follows the Semantic Versioning format, with `*.*.*` meaning any version.
820+
* A type representing a mod's dependency.
821+
* The `version` field is the version of the dependency
822+
* The `url` field is the Github Repository's url to download the dependency from
823+
* The `commit` field is the commit hash of the dependency
824824
*/
825-
typedef ModDependencies = Map<String, VersionRule>;
825+
typedef ModDependency =
826+
{
827+
version:VersionRule,
828+
?url:String,
829+
?commit:String
830+
};
826831

827832
/**
828833
* A type representing data about a mod, as retrieved from its metadata file.
@@ -894,14 +899,14 @@ class ModMetadata
894899
* These other mods must be also be loaded in order for this mod to load,
895900
* and this mod must be loaded after the dependencies.
896901
*/
897-
public var dependencies:ModDependencies;
902+
public var dependencies:Map<String, ModDependency>;
898903

899904
/**
900905
* A list of dependencies.
901906
* This mod must be loaded after the optional dependencies,
902907
* but those mods do not necessarily need to be loaded.
903908
*/
904-
public var optionalDependencies:ModDependencies;
909+
public var optionalDependencies:Map<String, ModDependency>;
905910

906911
/**
907912
* A deprecated field representing the mod's author.
@@ -1014,8 +1019,8 @@ class ModMetadata
10141019
m.license = JsonHelp.str(json, 'license');
10151020
m.metadata = JsonHelp.mapStr(json, 'metadata');
10161021

1017-
m.dependencies = JsonHelp.mapVersionRule(json, 'dependencies');
1018-
m.optionalDependencies = JsonHelp.mapVersionRule(json, 'optionalDependencies');
1022+
m.dependencies = JsonHelp.mapModDependency(json, 'dependencies');
1023+
m.optionalDependencies = JsonHelp.mapModDependency(json, 'optionalDependencies');
10191024

10201025
return m;
10211026
}
@@ -1166,6 +1171,11 @@ enum abstract PolymodErrorCode(String) from String to String
11661171
*/
11671172
var DEPENDENCY_CHECK_SKIPPED:String = 'dependency_check_skipped';
11681173

1174+
/**
1175+
* Polymod attempted to download a mod, but it failed to do so.
1176+
*/
1177+
var DEPENDENCY_NOT_DOWNLOADABLE:String = 'dependency_not_downloadable';
1178+
11691179
/**
11701180
* The given mod's API version does not match the version rule passed to Polymod.init.
11711181
* - This generally indicates the mod is outdated and should be updated by the author.

polymod/format/JsonHelp.hx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package polymod.format;
22

3+
import polymod.Polymod.ModDependency;
34
import thx.semver.VersionRule;
45

56
class JsonHelp
@@ -89,6 +90,25 @@ class JsonHelp
8990
return map;
9091
}
9192

93+
public static function mapModDependency(json:Dynamic, field:String):Map<String, ModDependency>
94+
{
95+
var map:Map<String, ModDependency> = new Map<String, ModDependency>();
96+
if (json == null || field == '' || field == null)
97+
return map;
98+
var val = null;
99+
if (Reflect.hasField(json, field))
100+
val = Reflect.field(json, field);
101+
if (val != null)
102+
{
103+
for (field in Reflect.fields(val))
104+
{
105+
var fieldVal = Reflect.field(val, field);
106+
map.set(field, fieldVal);
107+
}
108+
}
109+
return map;
110+
}
111+
92112
public static function str(json:Dynamic, field:String, defaultValue:String = ''):String
93113
{
94114
var str:String = '';

polymod/util/DependencyUtil.hx

Lines changed: 108 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package polymod.util;
22

3-
import polymod.Polymod.ModDependencies;
3+
import haxe.Http;
4+
import haxe.io.Bytes;
5+
import haxe.zip.Reader;
6+
import polymod.Polymod.ModDependency;
47
import polymod.Polymod.ModMetadata;
8+
import sys.FileSystem;
9+
import sys.io.File;
510
import thx.semver.VersionRule;
611

712
/**
@@ -53,7 +58,7 @@ class DependencyUtil
5358
var result:Array<ModMetadata> = [];
5459

5560
// Compile a map of mod dependencies.
56-
var deps:ModDependencies = compileDependencies(modList);
61+
var deps:Map<String, ModDependency> = compileDependencies(modList);
5762

5863
// Check that all mods are in the mod list.
5964
var relevantMods:Array<ModMetadata> = [];
@@ -68,7 +73,7 @@ class DependencyUtil
6873
// Check that all dependencies are satisfied.
6974
for (dep in deps.keys())
7075
{
71-
var depRule:VersionRule = deps.get(dep);
76+
var depRule:VersionRule = deps.get(dep).version;
7277

7378
// Check that the dependency is in the mod list.
7479
var depMod:ModMetadata = null;
@@ -112,7 +117,7 @@ class DependencyUtil
112117
static function validateDependencies(modList:Array<ModMetadata>):Bool
113118
{
114119
// Compile a map of mod dependencies.
115-
var deps:ModDependencies = compileDependencies(modList);
120+
var deps:Map<String, ModDependency> = compileDependencies(modList);
116121

117122
// Check that all mods are in the mod list.
118123
var relevantMods:Array<ModMetadata> = [];
@@ -127,7 +132,7 @@ class DependencyUtil
127132
// Check that all dependencies are satisfied.
128133
for (dep in deps.keys())
129134
{
130-
var depRule:VersionRule = deps.get(dep);
135+
var depRule:VersionRule = deps.get(dep).version;
131136

132137
// Check that the dependency is in the mod list.
133138
var depMod:ModMetadata = null;
@@ -307,33 +312,124 @@ class DependencyUtil
307312
* For example, if one mod requires `>1.2.0` of `modA` and another requires `>1.3.0` of `modA`,
308313
* the merged list will be `[modA: '>1.2.0 && >1.3.0']`.
309314
*/
310-
public static function compileDependencies(modList:Array<ModMetadata>):Map<String, VersionRule>
315+
public static function compileDependencies(modList:Array<ModMetadata>):Map<String, ModDependency>
311316
{
312-
var result:Map<String, VersionRule> = [];
317+
var result:Map<String, ModDependency> = [];
313318

314319
for (mod in modList)
315320
{
316321
if (result[mod.id] == null)
317-
result[mod.id] = VersionUtil.DEFAULT_VERSION_RULE;
322+
result[mod.id] = {
323+
version: VersionUtil.DEFAULT_VERSION_RULE
324+
};
318325

319326
if (mod.dependencies != null)
320327
{
321328
for (dependencyId in mod.dependencies.keys())
322329
{
323-
var dependencyRule:VersionRule = mod.dependencies[dependencyId];
330+
if (result[dependencyId] == null)
331+
result[dependencyId] = {
332+
version: VersionUtil.DEFAULT_VERSION_RULE
333+
};
324334

325-
if (result[dependencyId] != null)
335+
result[dependencyId].url = mod.dependencies[dependencyId].url;
336+
result[dependencyId].commit = mod.dependencies[dependencyId].commit;
337+
var dependencyRule:VersionRule = mod.dependencies[dependencyId].version;
338+
339+
if (result[dependencyId].version != null)
326340
{
327-
result[dependencyId] = VersionUtil.combineRulesAnd(result[dependencyId], dependencyRule);
341+
result[dependencyId].version = VersionUtil.combineRulesAnd(result[dependencyId].version, dependencyRule);
328342
}
329343
else
330344
{
331-
result[dependencyId] = dependencyRule;
345+
result[dependencyId].version = dependencyRule;
332346
}
333347
}
334348
}
335349
}
336350

337351
return result;
338352
}
353+
354+
/**
355+
* Given a github url and commit hash, download the dependency.
356+
* This is done using an http request.
357+
* @param outputDir the directory to download the dependency to
358+
* @param url github repository url: https://github.com/OWNER/REPOSITORY
359+
* @param commit commit hash
360+
*/
361+
public static function downloadDependency(outputDir:String, url:String, commit:String):Void
362+
{
363+
var zipBytes:Null<Bytes> = getDependencyAsZip(url, commit);
364+
365+
if (zipBytes == null || zipBytes.length == 0)
366+
return;
367+
368+
var reader = new Reader(new haxe.io.BytesInput(zipBytes));
369+
var entries = reader.read();
370+
371+
if (!FileSystem.exists(outputDir))
372+
{
373+
FileSystem.createDirectory(outputDir);
374+
}
375+
376+
for (entry in entries)
377+
{
378+
var fileName = entry.fileName;
379+
if (entry.fileSize > 0)
380+
{
381+
var content = entry.data;
382+
var filePath = outputDir + "/" + fileName;
383+
384+
var dirs = fileName.split("/");
385+
dirs.pop();
386+
var currentDir = outputDir;
387+
for (dir in dirs)
388+
{
389+
currentDir += "/" + dir;
390+
if (!FileSystem.exists(currentDir))
391+
{
392+
FileSystem.createDirectory(currentDir);
393+
}
394+
}
395+
396+
File.saveBytes(filePath, content);
397+
trace("Extracted: " + filePath);
398+
}
399+
}
400+
401+
trace("Extraction complete. Files saved in: " + outputDir);
402+
}
403+
404+
/**
405+
* Get the bytes of a dependency as a zip file.
406+
* @param url github repository url: https://github.com/OWNER/REPOSITORY
407+
* @param commit commit hash
408+
* @return Null<Bytes>
409+
*/
410+
public static function getDependencyAsZip(url:String, commit:String):Null<Bytes>
411+
{
412+
// url structure of the code to download
413+
// https://codeload.github.com/OWNER/REPOSITORY/zip/COMMIT
414+
415+
var urlSplit = url.split('/');
416+
var repository = urlSplit[urlSplit.length - 1];
417+
var owner = urlSplit[urlSplit.length - 2];
418+
419+
var downloadUrl = 'https://codeload.github.com/${owner}/${repository}/zip/${commit}';
420+
421+
var zipBytes:Null<Bytes> = null;
422+
423+
var http = new Http(downloadUrl);
424+
http.onBytes = function(bytes:Bytes)
425+
{
426+
zipBytes = bytes;
427+
}
428+
http.request(false);
429+
430+
if (zipBytes == null || zipBytes.length == 0)
431+
Polymod.error(DEPENDENCY_NOT_DOWNLOADABLE, 'Failed to download dependency: ${downloadUrl}');
432+
433+
return zipBytes;
434+
}
339435
}

0 commit comments

Comments
 (0)