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";