1 /** 2 A package supplier, able to get some packages to the local FS. 3 4 Copyright: © 2012-2013 Matthias Dondorff 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Matthias Dondorff 7 */ 8 module dub.packagesupplier; 9 10 import dub.dependency; 11 import dub.internal.utils; 12 import dub.internal.vibecompat.core.log; 13 import dub.internal.vibecompat.core.file; 14 import dub.internal.vibecompat.data.json; 15 import dub.internal.vibecompat.inet.url; 16 17 import std.file; 18 import std.exception; 19 import std.zip; 20 import std.conv; 21 22 /// Supplies packages, this is done by supplying the latest possible version 23 /// which is available. 24 interface PackageSupplier { 25 /// Returns a hunman readable representation of the supplier 26 @property string description(); 27 28 /// path: absolute path to store the package (usually in a zip format) 29 void retrievePackage(Path path, string packageId, Dependency dep, bool pre_release); 30 31 /// returns the metadata for the package 32 Json getPackageDescription(string packageId, Dependency dep, bool pre_release); 33 } 34 35 class FileSystemPackageSupplier : PackageSupplier { 36 private { 37 Path m_path; 38 } 39 40 this(Path root) { m_path = root; } 41 42 override @property string description() { return "file repository at "~m_path.toNativeString(); } 43 44 void retrievePackage(Path path, string packageId, Dependency dep, bool pre_release) 45 { 46 enforce(path.absolute); 47 logInfo("Storing package '"~packageId~"', version requirements: %s", dep); 48 auto filename = bestPackageFile(packageId, dep, pre_release); 49 enforce(existsFile(filename)); 50 copyFile(filename, path); 51 } 52 53 Json getPackageDescription(string packageId, Dependency dep, bool pre_release) 54 { 55 auto filename = bestPackageFile(packageId, dep, pre_release); 56 return jsonFromZip(filename, "package.json"); 57 } 58 59 private Path bestPackageFile(string packageId, Dependency dep, bool pre_release) 60 const { 61 Version bestver = Version.RELEASE; 62 foreach (DirEntry d; dirEntries(m_path.toNativeString(), packageId~"*", SpanMode.shallow)) { 63 Path p = Path(d.name); 64 logDebug("Entry: %s", p); 65 enforce(to!string(p.head)[$-4..$] == ".zip"); 66 string vers = to!string(p.head)[packageId.length+1..$-4]; 67 logDebug("Version string: "~vers); 68 Version cur = Version(vers); 69 if (!dep.matches(cur)) continue; 70 if (bestver == Version.RELEASE) bestver = cur; 71 else if (pre_release) { 72 if (cur > bestver) bestver = cur; 73 } else if (bestver.isPreRelease) { 74 if (!cur.isPreRelease || cur > bestver) bestver = cur; 75 } else if (!cur.isPreRelease && cur > bestver) bestver = cur; 76 } 77 78 auto fileName = m_path ~ (packageId ~ "_" ~ to!string(bestver) ~ ".zip"); 79 80 if (bestver == Version.RELEASE || !existsFile(fileName)) 81 throw new Exception("No matching package found"); 82 83 logDiagnostic("Found best matching package: '%s'", fileName); 84 return fileName; 85 } 86 } 87 88 89 /// Client PackageSupplier using the registry available via registerVpmRegistry 90 class RegistryPackageSupplier : PackageSupplier { 91 private { 92 Url m_registryUrl; 93 Json[string] m_allMetadata; 94 } 95 96 this(Url registry) 97 { 98 m_registryUrl = registry; 99 } 100 101 override @property string description() { return "registry at "~m_registryUrl.toString(); } 102 103 void retrievePackage(Path path, string packageId, Dependency dep, bool pre_release) 104 { 105 Json best = getBestPackage(packageId, dep, pre_release); 106 auto url = m_registryUrl ~ Path(PackagesPath~"/"~packageId~"/"~best["version"].get!string~".zip"); 107 logDiagnostic("Found download URL: '%s'", url); 108 download(url, path); 109 } 110 111 Json getPackageDescription(string packageId, Dependency dep, bool pre_release) 112 { 113 return getBestPackage(packageId, dep, pre_release); 114 } 115 116 private Json getMetadata(string packageId) 117 { 118 if (auto json = packageId in m_allMetadata) 119 return *json; 120 121 auto url = m_registryUrl ~ Path(PackagesPath ~ "/" ~ packageId ~ ".json"); 122 123 logDebug("Downloading metadata for %s", packageId); 124 logDebug("Getting from %s", url); 125 126 auto jsonData = cast(string)download(url); 127 Json json = parseJson(jsonData); 128 m_allMetadata[packageId] = json; 129 return json; 130 } 131 132 private Json getBestPackage(string packageId, Dependency dep, bool pre_release) 133 { 134 Json md = getMetadata(packageId); 135 Json best = null; 136 Version bestver; 137 foreach (json; md["versions"]) { 138 auto cur = Version(cast(string)json["version"]); 139 if (!dep.matches(cur)) continue; 140 if (best == null) best = json; 141 else if (pre_release) { 142 if (cur > bestver) best = json; 143 } else if (bestver.isPreRelease) { 144 if (!cur.isPreRelease || cur > bestver) best = json; 145 } else if (!cur.isPreRelease && cur > bestver) best = json; 146 bestver = Version(cast(string)best["version"]); 147 } 148 enforce(best != null, "No package candidate found for "~packageId~" "~dep.toString()); 149 return best; 150 } 151 } 152 153 private enum PackagesPath = "packages";