Newer
Older
dub_jkp / source / dub / packagesupplier.d
  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.algorithm : filter, sort;
  18. import std.array : array;
  19. import std.conv;
  20. import std.datetime;
  21. import std.exception;
  22. import std.file;
  23. import std.string : format;
  24. import std.zip;
  25.  
  26. // TODO: drop the "best package" behavior and let retrievePackage/getPackageDescription take a Version instead of Dependency
  27.  
  28. /// Supplies packages, this is done by supplying the latest possible version
  29. /// which is available.
  30. interface PackageSupplier {
  31. /// Returns a hunman readable representation of the supplier
  32. @property string description();
  33.  
  34. Version[] getVersions(string package_id);
  35.  
  36. /// path: absolute path to store the package (usually in a zip format)
  37. void retrievePackage(Path path, string packageId, Dependency dep, bool pre_release);
  38. /// returns the metadata for the package
  39. Json getPackageDescription(string packageId, Dependency dep, bool pre_release);
  40. }
  41.  
  42. class FileSystemPackageSupplier : PackageSupplier {
  43. private {
  44. Path m_path;
  45. }
  46.  
  47. this(Path root) { m_path = root; }
  48.  
  49. override @property string description() { return "file repository at "~m_path.toNativeString(); }
  50. Version[] getVersions(string package_id)
  51. {
  52. Version[] ret;
  53. foreach (DirEntry d; dirEntries(m_path.toNativeString(), package_id~"*", SpanMode.shallow)) {
  54. Path p = Path(d.name);
  55. logDebug("Entry: %s", p);
  56. enforce(to!string(p.head)[$-4..$] == ".zip");
  57. auto vers = p.head.toString()[package_id.length+1..$-4];
  58. logDebug("Version: %s", vers);
  59. ret ~= Version(vers);
  60. }
  61. ret.sort();
  62. return ret;
  63. }
  64.  
  65. void retrievePackage(Path path, string packageId, Dependency dep, bool pre_release)
  66. {
  67. enforce(path.absolute);
  68. logInfo("Storing package '"~packageId~"', version requirements: %s", dep);
  69. auto filename = bestPackageFile(packageId, dep, pre_release);
  70. enforce(existsFile(filename));
  71. copyFile(filename, path);
  72. }
  73. Json getPackageDescription(string packageId, Dependency dep, bool pre_release)
  74. {
  75. auto filename = bestPackageFile(packageId, dep, pre_release);
  76. return jsonFromZip(filename, "package.json");
  77. }
  78. private Path bestPackageFile(string packageId, Dependency dep, bool pre_release)
  79. {
  80. Path toPath(Version ver) {
  81. return m_path ~ (packageId ~ "-" ~ ver.toString() ~ ".zip");
  82. }
  83. auto versions = getVersions(packageId).filter!(v => dep.matches(v)).array;
  84. enforce(versions.length > 0, format("No package %s found matching %s", packageId, dep));
  85. foreach_reverse (ver; versions) {
  86. if (pre_release || !ver.isPreRelease)
  87. return toPath(ver);
  88. }
  89. return toPath(versions[$-1]);
  90. }
  91. }
  92.  
  93.  
  94. /// Client PackageSupplier using the registry available via registerVpmRegistry
  95. class RegistryPackageSupplier : PackageSupplier {
  96. private {
  97. URL m_registryUrl;
  98. struct CacheEntry { Json data; SysTime cacheTime; }
  99. CacheEntry[string] m_metadataCache;
  100. Duration m_maxCacheTime;
  101. }
  102. this(URL registry)
  103. {
  104. m_registryUrl = registry;
  105. m_maxCacheTime = 24.hours();
  106. }
  107.  
  108. override @property string description() { return "registry at "~m_registryUrl.toString(); }
  109.  
  110. Version[] getVersions(string package_id)
  111. {
  112. Version[] ret;
  113. Json md = getMetadata(package_id);
  114. foreach (json; md["versions"]) {
  115. auto cur = Version(cast(string)json["version"]);
  116. ret ~= cur;
  117. }
  118. ret.sort();
  119. return ret;
  120. }
  121. void retrievePackage(Path path, string packageId, Dependency dep, bool pre_release)
  122. {
  123. import std.array : replace;
  124. Json best = getBestPackage(packageId, dep, pre_release);
  125. auto vers = replace(best["version"].get!string, "+", "%2B");
  126. auto url = m_registryUrl ~ Path(PackagesPath~"/"~packageId~"/"~vers~".zip");
  127. logDiagnostic("Found download URL: '%s'", url);
  128. download(url, path);
  129. }
  130. Json getPackageDescription(string packageId, Dependency dep, bool pre_release)
  131. {
  132. return getBestPackage(packageId, dep, pre_release);
  133. }
  134. private Json getMetadata(string packageId)
  135. {
  136. auto now = Clock.currTime(UTC());
  137. if (auto pentry = packageId in m_metadataCache) {
  138. if (pentry.cacheTime + m_maxCacheTime > now)
  139. return pentry.data;
  140. }
  141.  
  142. auto url = m_registryUrl ~ Path(PackagesPath ~ "/" ~ packageId ~ ".json");
  143. logDebug("Downloading metadata for %s", packageId);
  144. logDebug("Getting from %s", url);
  145.  
  146. auto jsonData = cast(string)download(url);
  147. Json json = parseJson(jsonData);
  148. m_metadataCache[packageId] = CacheEntry(json, now);
  149. return json;
  150. }
  151. private Json getBestPackage(string packageId, Dependency dep, bool pre_release)
  152. {
  153. Json md = getMetadata(packageId);
  154. Json best = null;
  155. Version bestver;
  156. foreach (json; md["versions"]) {
  157. auto cur = Version(cast(string)json["version"]);
  158. if (!dep.matches(cur)) continue;
  159. if (best == null) best = json;
  160. else if (pre_release) {
  161. if (cur > bestver) best = json;
  162. } else if (bestver.isPreRelease) {
  163. if (!cur.isPreRelease || cur > bestver) best = json;
  164. } else if (!cur.isPreRelease && cur > bestver) best = json;
  165. bestver = Version(cast(string)best["version"]);
  166. }
  167. enforce(best != null, "No package candidate found for "~packageId~" "~dep.toString());
  168. return best;
  169. }
  170. }
  171.  
  172. private enum PackagesPath = "packages";