1 /** 2 Copyright: © 2013 rejectedsoftware e.K. 3 License: Subject to the terms of the GNU GPLv3 license, as written in the included LICENSE.txt file. 4 Authors: Sönke Ludwig 5 */ 6 module dubregistry.repositories.bitbucket; 7 8 import dubregistry.cache; 9 import dubregistry.dbcontroller : DbRepository; 10 import dubregistry.repositories.repository; 11 import std..string : format, startsWith; 12 import std.typecons; 13 import vibe.core.log; 14 import vibe.core.stream; 15 import vibe.data.json; 16 import vibe.inet.url; 17 18 19 class BitbucketRepository : Repository { 20 @safe: 21 22 private { 23 string m_owner; 24 string m_project; 25 } 26 27 static void register() 28 { 29 Repository factory(DbRepository info) @safe { 30 return new BitbucketRepository(info.owner, info.project); 31 } 32 addRepositoryFactory("bitbucket", &factory); 33 } 34 35 this(string owner, string project) 36 { 37 m_owner = owner; 38 m_project = project; 39 } 40 41 RefInfo[] getTags() 42 { 43 Json tags; 44 try tags = readJson("https://api.bitbucket.org/1.0/repositories/"~m_owner~"/"~m_project~"/tags"); 45 catch( Exception e ) { throw new Exception("Failed to get tags: "~e.msg); } 46 RefInfo[] ret; 47 foreach (string tagname, tag; tags.byKeyValue) { 48 try { 49 auto commit_hash = tag["raw_node"].get!string(); 50 auto commit_date = bbToIsoDate(tag["utctimestamp"].get!string()); 51 ret ~= RefInfo(tagname, commit_hash, commit_date); 52 logDebug("Found tag for %s/%s: %s", m_owner, m_project, tagname); 53 } catch( Exception e ){ 54 throw new Exception("Failed to process tag "~tag["name"].get!string~": "~e.msg); 55 } 56 } 57 return ret; 58 } 59 60 RefInfo[] getBranches() 61 { 62 Json branches = readJson("https://api.bitbucket.org/1.0/repositories/"~m_owner~"/"~m_project~"/branches"); 63 RefInfo[] ret; 64 foreach (string branchname, branch; branches.byKeyValue) { 65 auto commit_hash = branch["raw_node"].get!string(); 66 auto commit_date = bbToIsoDate(branch["utctimestamp"].get!string()); 67 ret ~= RefInfo(branchname, commit_hash, commit_date); 68 logDebug("Found branch for %s/%s: %s", m_owner, m_project, branchname); 69 } 70 return ret; 71 } 72 73 RepositoryInfo getInfo() 74 { 75 auto nfo = readJson("https://api.bitbucket.org/1.0/repositories/"~m_owner~"/"~m_project); 76 RepositoryInfo ret; 77 ret.isFork = nfo["is_fork"].opt!bool; 78 ret.stats.watchers = nfo["followers_count"].opt!uint; 79 ret.stats.forks = nfo["forks_count"].opt!uint; 80 return ret; 81 } 82 83 RepositoryFile[] listFiles(string commit_sha, InetPath path) 84 { 85 assert(path.absolute, "Passed relative path to listFiles."); 86 auto url = "https://bitbucket.org/api/2.0/repositories/"~m_owner~"/"~m_project~"/src/"~commit_sha~path.toString()~"?pagelen=100"; 87 auto ls = readJson(url)["values"].get!(Json[]); 88 RepositoryFile[] ret; 89 ret.reserve(ls.length); 90 foreach (entry; ls) { 91 string type = entry["type"].get!string; 92 RepositoryFile file; 93 if (type == "commit_directory") { 94 file.type = RepositoryFile.Type.directory; 95 } 96 else if (type == "commit_file") { 97 file.type = RepositoryFile.Type.file; 98 file.size = entry["size"].get!size_t; 99 } 100 else continue; 101 file.commitSha = entry["commit"]["hash"].get!string; 102 file.path = InetPath("/" ~ entry["path"].get!string); 103 ret ~= file; 104 } 105 return ret; 106 } 107 108 void readFile(string commit_sha, InetPath path, scope void delegate(scope InputStream) @safe reader) 109 { 110 assert(path.absolute, "Passed relative path to readFile."); 111 auto url = "https://bitbucket.org/api/1.0/repositories/"~m_owner~"/"~m_project~"/raw/"~commit_sha~path.toString(); 112 downloadCached(url, (scope input) @safe { 113 reader(input); 114 }, true); 115 } 116 117 string getDownloadUrl(string ver) 118 { 119 import std.uri : encodeComponent; 120 if( ver.startsWith("~") ) ver = ver[1 .. $]; 121 else ver = ver; 122 auto venc = () @trusted { return encodeComponent(ver); } (); 123 return "https://bitbucket.org/"~m_owner~"/"~m_project~"/get/"~venc~".zip"; 124 } 125 126 void download(string ver, scope void delegate(scope InputStream) @safe del) 127 { 128 downloadCached(getDownloadUrl(ver), del); 129 } 130 } 131 132 private auto bbToIsoDate(string bbdate) 133 @safe { 134 import std.array, std.datetime : SysTime; 135 auto ttz = bbdate.split("+"); 136 if( ttz.length < 2 ) ttz ~= "00:00"; 137 auto parts = ttz[0].split("-"); 138 parts = parts[0 .. $-1] ~ parts[$-1].split(" "); 139 parts = parts[0 .. $-1] ~ parts[$-1].split(":"); 140 141 return SysTime.fromISOString(format("%s%s%sT%s%s%s+%s", parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], ttz[1])); 142 }