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