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 }