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 	/// path: absolute path to store the package (usually in a zip format)
26 	void retrievePackage(Path path, string packageId, Dependency dep);
27 	
28 	/// returns the metadata for the package
29 	Json getPackageDescription(string packageId, Dependency dep);
30 
31 	/// Returns a hunman readable representation of the supplier
32 	string toString();
33 }
34 
35 class FileSystemPackageSupplier : PackageSupplier {
36 	private {
37 		Path m_path;
38 	}
39 
40 	this(Path root) { m_path = root; }
41 
42 	override string toString() { return "file repository at "~m_path.toNativeString(); }
43 	
44 	void retrievePackage(Path path, string packageId, Dependency dep)
45 	{
46 		enforce(path.absolute);
47 		logInfo("Storing package '"~packageId~"', version requirements: %s", dep);
48 		auto filename = bestPackageFile(packageId, dep);
49 		enforce(existsFile(filename));
50 		copyFile(filename, path);
51 	}
52 	
53 	Json getPackageDescription(string packageId, Dependency dep)
54 	{
55 		auto filename = bestPackageFile(packageId, dep);
56 		return jsonFromZip(filename, "package.json");
57 	}
58 	
59 	private Path bestPackageFile(string packageId, Dependency dep)
60 	const {
61 		Version bestVersion = 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 v = Version(vers);
69 			if (v > bestVersion && dep.matches(v)) {
70 				bestVersion = v;
71 			}
72 		}
73 		
74 		auto fileName = m_path ~ (packageId ~ "_" ~ to!string(bestVersion) ~ ".zip");
75 		
76 		if (bestVersion == Version.RELEASE || !existsFile(fileName))
77 			throw new Exception("No matching package found");
78 		
79 		logDiagnostic("Found best matching package: '%s'", fileName);
80 		return fileName;
81 	}
82 }
83 
84 
85 /// Client PackageSupplier using the registry available via registerVpmRegistry
86 class RegistryPackageSupplier : PackageSupplier {
87 	private {
88 		Url m_registryUrl;
89 		Json[string] m_allMetadata;
90 	}
91 	
92 	this(Url registry)
93 	{
94 		m_registryUrl = registry;
95 	}
96 
97 	override string toString() { return "registry at "~m_registryUrl.toString(); }
98 	
99 	void retrievePackage(Path path, string packageId, Dependency dep)
100 	{
101 		Json best = getBestPackage(packageId, dep);
102 		auto url = m_registryUrl ~ Path(PackagesPath~"/"~packageId~"/"~best["version"].get!string~".zip");
103 		logDiagnostic("Found download URL: '%s'", url);
104 		download(url, path);
105 	}
106 	
107 	Json getPackageDescription(string packageId, Dependency dep)
108 	{
109 		return getBestPackage(packageId, dep);
110 	}
111 	
112 	private Json getMetadata(string packageId)
113 	{
114 		if (auto json = packageId in m_allMetadata)
115 			return *json;
116 
117 		auto url = m_registryUrl ~ Path(PackagesPath ~ "/" ~ packageId ~ ".json");
118 		
119 		logDebug("Downloading metadata for %s", packageId);
120 		logDebug("Getting from %s", url);
121 
122 		auto jsonData = cast(string)download(url);
123 		Json json = parseJson(jsonData);
124 		m_allMetadata[packageId] = json;
125 		return json;
126 	}
127 	
128 	private Json getBestPackage(string packageId, Dependency dep)
129 	{
130 		Json md = getMetadata(packageId);
131 		Json best = null;
132 		foreach (json; md["versions"]) {
133 			auto cur = Version(cast(string)json["version"]);
134 			if (dep.matches(cur) && (best == null || Version(cast(string)best["version"]) < cur))
135 				best = json;
136 		}
137 		enforce(best != null, "No package candidate found for "~packageId~" "~dep.toString());
138 		return best;
139 	}
140 }
141 
142 private enum PackagesPath = "packages";