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 private { 21 string m_owner; 22 string m_project; 23 } 24 25 static void register() 26 { 27 Repository factory(DbRepository info){ 28 return new BitbucketRepository(info.owner, info.project); 29 } 30 addRepositoryFactory("bitbucket", &factory); 31 } 32 33 this(string owner, string project) 34 { 35 m_owner = owner; 36 m_project = project; 37 } 38 39 RefInfo[] getTags() 40 { 41 Json tags; 42 try tags = readJson("https://api.bitbucket.org/1.0/repositories/"~m_owner~"/"~m_project~"/tags"); 43 catch( Exception e ) { throw new Exception("Failed to get tags: "~e.msg); } 44 RefInfo[] ret; 45 foreach( string tagname, tag; tags ){ 46 try { 47 auto commit_hash = tag["raw_node"].get!string(); 48 auto commit_date = bbToIsoDate(tag["utctimestamp"].get!string()); 49 ret ~= RefInfo(tagname, commit_hash, commit_date); 50 logDebug("Found tag for %s/%s: %s", m_owner, m_project, tagname); 51 } catch( Exception e ){ 52 throw new Exception("Failed to process tag "~tag["name"].get!string~": "~e.msg); 53 } 54 } 55 return ret; 56 } 57 58 RefInfo[] getBranches() 59 { 60 Json branches = readJson("https://api.bitbucket.org/1.0/repositories/"~m_owner~"/"~m_project~"/branches"); 61 RefInfo[] ret; 62 foreach( string branchname, branch; branches ){ 63 auto commit_hash = branch["raw_node"].get!string(); 64 auto commit_date = bbToIsoDate(branch["utctimestamp"].get!string()); 65 ret ~= RefInfo(branchname, commit_hash, commit_date); 66 logDebug("Found branch for %s/%s: %s", m_owner, m_project, branchname); 67 } 68 return ret; 69 } 70 71 RepositoryInfo getInfo() 72 { 73 auto nfo = readJson("https://api.bitbucket.org/1.0/repositories/"~m_owner~"/"~m_project); 74 RepositoryInfo ret; 75 ret.isFork = nfo["is_fork"].opt!bool; 76 ret.stats.watchers = nfo["followers_count"].opt!uint; 77 ret.stats.forks = nfo["forks_count"].opt!uint; 78 return ret; 79 } 80 81 void readFile(string commit_sha, Path path, scope void delegate(scope InputStream) reader) 82 { 83 assert(path.absolute, "Passed relative path to readFile."); 84 auto url = "https://bitbucket.org/api/1.0/repositories/"~m_owner~"/"~m_project~"/raw/"~commit_sha~path.toString(); 85 downloadCached(url, (scope input) { 86 reader(input); 87 }, true); 88 } 89 90 string getDownloadUrl(string ver) 91 { 92 import std.uri : encodeComponent; 93 if( ver.startsWith("~") ) ver = ver[1 .. $]; 94 else ver = ver; 95 return "https://bitbucket.org/"~m_owner~"/"~m_project~"/get/"~encodeComponent(ver)~".zip"; 96 } 97 98 void download(string ver, scope void delegate(scope InputStream) del) 99 { 100 downloadCached(getDownloadUrl(ver), del); 101 } 102 } 103 104 private auto bbToIsoDate(string bbdate) 105 { 106 import std.array, std.datetime : SysTime; 107 auto ttz = bbdate.split("+"); 108 if( ttz.length < 2 ) ttz ~= "00:00"; 109 auto parts = ttz[0].split("-"); 110 parts = parts[0 .. $-1] ~ parts[$-1].split(" "); 111 parts = parts[0 .. $-1] ~ parts[$-1].split(":"); 112 113 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])); 114 }