1 module dub.packagesuppliers.fallback; 2 3 import dub.packagesuppliers.packagesupplier; 4 import std.typecons : AutoImplement; 5 6 package abstract class AbstractFallbackPackageSupplier : PackageSupplier 7 { 8 protected import core.time : minutes; 9 protected import std.datetime : Clock, SysTime; 10 11 static struct Pair { PackageSupplier ps; SysTime failTime; } 12 protected Pair[] m_suppliers; 13 14 this(PackageSupplier[] suppliers) 15 { 16 assert(suppliers.length); 17 m_suppliers.length = suppliers.length; 18 foreach (i, ps; suppliers) 19 m_suppliers[i].ps = ps; 20 } 21 22 override @property string description() 23 { 24 import std.algorithm.iteration : map; 25 import std.format : format; 26 return format("%s (fallbacks %-(%s, %))", m_suppliers[0].ps.description, 27 m_suppliers[1 .. $].map!(pair => pair.ps.description)); 28 } 29 30 // Workaround https://issues.dlang.org/show_bug.cgi?id=2525 31 abstract override Version[] getVersions(in PackageName name); 32 abstract override ubyte[] fetchPackage(in PackageName name, in VersionRange dep, bool pre_release); 33 abstract override Json fetchPackageRecipe(in PackageName name, in VersionRange dep, bool pre_release); 34 abstract override SearchResult[] searchPackages(string query); 35 } 36 37 38 /** 39 Combines two package suppliers and uses the second as fallback to handle failures. 40 41 Assumes that both registries serve the same packages (--mirror). 42 */ 43 package(dub) alias FallbackPackageSupplier = AutoImplement!(AbstractFallbackPackageSupplier, fallback); 44 45 private template fallback(T, alias func) 46 { 47 import std.format : format; 48 enum fallback = q{ 49 import dub.internal.logging : logDebug; 50 51 Exception firstEx; 52 try 53 return m_suppliers[0].ps.%1$s(args); 54 catch (Exception e) 55 { 56 logDebug("Package supplier %%s failed with '%%s', trying fallbacks.", 57 m_suppliers[0].ps.description, e.msg); 58 firstEx = e; 59 } 60 61 immutable now = Clock.currTime; 62 foreach (ref pair; m_suppliers[1 .. $]) 63 { 64 if (pair.failTime > now - 10.minutes) 65 continue; 66 try 67 { 68 scope (success) logDebug("Fallback %%s succeeded", pair.ps.description); 69 return pair.ps.%1$s(args); 70 } 71 catch (Exception e) 72 { 73 pair.failTime = now; 74 logDebug("Fallback package supplier %%s failed with '%%s'.", 75 pair.ps.description, e.msg); 76 } 77 } 78 throw firstEx; 79 }.format(__traits(identifier, func)); 80 }