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