1 module dub.packagesuppliers.maven;
2 
3 import dub.packagesuppliers.packagesupplier;
4 
5 /**
6 	Maven repository based package supplier.
7 
8 	This package supplier connects to a maven repository
9 	to search for available packages.
10 */
11 class MavenRegistryPackageSupplier : PackageSupplier {
12 	import dub.internal.utils : retryDownload, HTTPStatusException;
13 	import dub.internal.vibecompat.data.json : serializeToJson;
14 	import dub.internal.vibecompat.core.log;
15 	import dub.internal.vibecompat.inet.url : URL;
16 
17 	import std.datetime : Clock, Duration, hours, SysTime, UTC;
18 
19 	private {
20 		URL m_mavenUrl;
21 		struct CacheEntry { Json data; SysTime cacheTime; }
22 		CacheEntry[string] m_metadataCache;
23 		Duration m_maxCacheTime;
24 	}
25 
26 	this(URL mavenUrl)
27 	{
28 		m_mavenUrl = mavenUrl;
29 		m_maxCacheTime = 24.hours();
30 	}
31 
32 	override @property string description() { return "maven repository at "~m_mavenUrl.toString(); }
33 
34 	Version[] getVersions(string package_id)
35 	{
36 		import std.algorithm.sorting : sort;
37 		auto md = getMetadata(package_id);
38 		if (md.type == Json.Type.null_)
39 			return null;
40 		Version[] ret;
41 		foreach (json; md["versions"]) {
42 			auto cur = Version(json["version"].get!string);
43 			ret ~= cur;
44 		}
45 		ret.sort();
46 		return ret;
47 	}
48 
49 	void fetchPackage(NativePath path, string packageId, Dependency dep, bool pre_release)
50 	{
51 		import std.format : format;
52 		auto md = getMetadata(packageId);
53 		Json best = getBestPackage(md, packageId, dep, pre_release);
54 		if (best.type == Json.Type.null_)
55 			return;
56 		auto vers = best["version"].get!string;
57 		auto url = m_mavenUrl~NativePath("%s/%s/%s-%s.zip".format(packageId, vers, packageId, vers));
58 
59 		try {
60 			retryDownload(url, path);
61 			return;
62 		}
63 		catch(HTTPStatusException e) {
64 			if (e.status == 404) throw e;
65 			else logDebug("Failed to download package %s from %s", packageId, url); 
66 		}
67 		catch(Exception e) {
68 			logDebug("Failed to download package %s from %s", packageId, url);
69 		}
70 		throw new Exception("Failed to download package %s from %s".format(packageId, url));
71 	}
72 
73 	Json fetchPackageRecipe(string packageId, Dependency dep, bool pre_release)
74 	{
75 		auto md = getMetadata(packageId);
76 		return getBestPackage(md, packageId, dep, pre_release);
77 	}
78 
79 	private Json getMetadata(string packageId)
80 	{
81 		import std.xml;
82 
83 		auto now = Clock.currTime(UTC());
84 		if (auto pentry = packageId in m_metadataCache) {
85 			if (pentry.cacheTime + m_maxCacheTime > now)
86 				return pentry.data;
87 			m_metadataCache.remove(packageId);
88 		}
89 
90 		auto url = m_mavenUrl~NativePath(packageId~"/maven-metadata.xml");
91 
92 		logDebug("Downloading maven metadata for %s", packageId);
93 		string xmlData;
94 
95 		try
96 			xmlData = cast(string)retryDownload(url);
97 		catch(HTTPStatusException e) {
98 			if (e.status == 404) {
99 				logDebug("Maven metadata %s not found at %s (404): %s", packageId, description, e.msg);
100 				return Json(null);
101 			}
102 			else throw e;
103 		}
104 
105 		auto json = Json(["name": Json(packageId), "versions": Json.emptyArray]);
106 		auto xml = new DocumentParser(xmlData);
107 
108 		xml.onStartTag["versions"] = (ElementParser xml) {
109 			 xml.onEndTag["version"] = (in Element e) {
110 				json["versions"] ~= serializeToJson(["name": packageId, "version": e.text]);
111 			 };
112 			 xml.parse();
113 		};
114 		xml.parse();
115 		m_metadataCache[packageId] = CacheEntry(json, now);
116 		return json;
117 	}
118 
119 	SearchResult[] searchPackages(string query)
120 	{
121 		return [];
122 	}
123 }
124