1 module dub.packagesuppliers.filesystem; 2 3 import dub.internal.logging; 4 import dub.internal.vibecompat.inet.path; 5 import dub.packagesuppliers.packagesupplier; 6 7 import std.exception : enforce; 8 9 /** 10 File system based package supplier. 11 12 This package supplier searches a certain directory for files with names of 13 the form "[package name]-[version].zip". 14 */ 15 class FileSystemPackageSupplier : PackageSupplier { 16 private { 17 NativePath m_path; 18 } 19 20 this(NativePath root) { m_path = root; } 21 22 override @property string description() { return "file repository at "~m_path.toNativeString(); } 23 24 Version[] getVersions(in PackageName name) 25 { 26 import std.algorithm.sorting : sort; 27 import std.file : dirEntries, DirEntry, SpanMode; 28 import std.conv : to; 29 import dub.semver : isValidVersion; 30 Version[] ret; 31 const zipFileGlob = name.main.toString() ~ "*.zip"; 32 foreach (DirEntry d; dirEntries(m_path.toNativeString(), zipFileGlob, SpanMode.shallow)) { 33 NativePath p = NativePath(d.name); 34 auto vers = p.head.name[name.main.toString().length+1..$-4]; 35 if (!isValidVersion(vers)) { 36 logDebug("Ignoring entry '%s' because it isn't a version of package '%s'", p, name.main); 37 continue; 38 } 39 logDebug("Entry: %s", p); 40 logDebug("Version: %s", vers); 41 ret ~= Version(vers); 42 } 43 ret.sort(); 44 return ret; 45 } 46 47 override ubyte[] fetchPackage(in PackageName name, 48 in VersionRange dep, bool pre_release) 49 { 50 import dub.internal.vibecompat.core.file : readFile, existsFile; 51 logInfo("Storing package '%s', version requirements: %s", name.main, dep); 52 auto filename = bestPackageFile(name, dep, pre_release); 53 enforce(existsFile(filename)); 54 return readFile(filename); 55 } 56 57 override Json fetchPackageRecipe(in PackageName name, in VersionRange dep, 58 bool pre_release) 59 { 60 import std.array : split; 61 import std.path : stripExtension; 62 import std.algorithm : startsWith, endsWith; 63 import dub.internal.utils : packageInfoFileFromZip; 64 import dub.recipe.io : parsePackageRecipe; 65 import dub.recipe.json : toJson; 66 67 auto filePath = bestPackageFile(name, dep, pre_release); 68 string packageFileName; 69 string packageFileContent = packageInfoFileFromZip(filePath, packageFileName); 70 auto recipe = parsePackageRecipe(packageFileContent, packageFileName); 71 Json json = toJson(recipe); 72 auto basename = filePath.head.name; 73 enforce(basename.endsWith(".zip"), "Malformed package filename: " ~ filePath.toNativeString); 74 enforce(basename.startsWith(name.main.toString()), 75 "Malformed package filename: " ~ filePath.toNativeString); 76 json["version"] = basename[name.main.toString().length + 1 .. $-4]; 77 return json; 78 } 79 80 SearchResult[] searchPackages(string query) 81 { 82 // TODO! 83 return null; 84 } 85 86 private NativePath bestPackageFile(in PackageName name, in VersionRange dep, 87 bool pre_release) 88 { 89 import std.algorithm.iteration : filter; 90 import std.array : array; 91 import std.format : format; 92 NativePath toPath(Version ver) { 93 return m_path ~ "%s-%s.zip".format(name.main, ver); 94 } 95 auto versions = getVersions(name).filter!(v => dep.matches(v)).array; 96 enforce(versions.length > 0, format("No package %s found matching %s", name.main, dep)); 97 foreach_reverse (ver; versions) { 98 if (pre_release || !ver.isPreRelease) 99 return toPath(ver); 100 } 101 return toPath(versions[$-1]); 102 } 103 }