1 /** 2 */ 3 module dubregistry.mirror; 4 5 import dubregistry.registry; 6 import dubregistry.dbcontroller; 7 import userman.db.controller; 8 import vibe.core.log; 9 import vibe.data.bson; 10 import vibe.http.client; 11 import vibe.inet.url; 12 import std.array : array; 13 import std.datetime : SysTime; 14 import std.encoding : sanitize; 15 import std.format : format; 16 17 void validateMirrorURL(ref string base_url) 18 { 19 import std.exception : enforce; 20 import std.algorithm.searching : endsWith; 21 import vibe.core.file : existsFile; 22 23 // Local JSON files are allowed 24 if (NativePath(base_url).existsFile) 25 return; 26 27 // ensure the URL has a trailing slash 28 if (!base_url.endsWith('/')) base_url ~= '/'; 29 30 // check two characteristic API endpoints 31 enum urls = ["packages/index.json", "api/packages/search?q=foobar"]; 32 foreach (url; urls) { 33 try { 34 requestHTTP(base_url ~ url, 35 (scope req) { req.method = HTTPMethod.HEAD; }, 36 (scope res) { 37 enforce(res.statusCode < 400, 38 format("Endpoint '%s' could not be accessed: %s", url, httpStatusText(res.statusCode))); 39 } 40 ); 41 } catch (Exception e) { 42 throw new Exception("The provided mirror URL does not appear to point to a valid DUB registry root: "~e.msg); 43 } 44 } 45 } 46 47 void mirrorRegistry(DubRegistry registry, string fileOrUrl) 48 nothrow { 49 logInfo("Polling '%s' for updates...", fileOrUrl); 50 try { 51 import vibe.core.file : existsFile, readFileUTF8; 52 53 DbPackage[] packs; 54 URL url; 55 auto path = NativePath(fileOrUrl); 56 57 if (path.existsFile) 58 { 59 packs = path.readFileUTF8.deserializeJson!(DbPackage[]); 60 } 61 else 62 { 63 url = URL(fileOrUrl); 64 packs = requestHTTP(url ~ InetPath("api/packages/dump")).readJson().deserializeJson!(DbPackage[]); 65 } 66 67 logInfo("Updates for '%s' downloaded.", url); 68 69 bool[BsonObjectID] current_packs; 70 foreach (p; packs) current_packs[p._id] = true; 71 72 // first, remove all packages that don't exist anymore to avoid possible name conflicts 73 foreach (id; registry.availablePackageIDs) 74 if (id !in current_packs) { 75 try { 76 auto pack = registry.db.getPackage(id); 77 logInfo("Removing package '%s", pack.name); 78 registry.removePackage(pack.name, User.ID(pack.owner)); 79 } catch (Exception e) { 80 logError("Failed to remove package with ID '%s': %s", id, e.msg); 81 logDiagnostic("Full error: %s", e.toString().sanitize); 82 } 83 } 84 85 // then add/update all existing packages 86 foreach (p; packs) { 87 try { 88 logInfo("Updating package '%s'", p.name); 89 registry.addOrSetPackage(p); 90 } catch (Exception e) { 91 logError("Failed to add/update package '%s': %s", p.name, e.msg); 92 logDiagnostic("Full error: %s", e.toString().sanitize); 93 } 94 } 95 96 logInfo("Updates for '%s' successfully processed.", fileOrUrl); 97 } catch (Exception e) { 98 logError("Fetching updated packages failed: %s", e.msg); 99 logDiagnostic("Full error: %s", e.toString().sanitize); 100 } 101 }