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 		return ret;
77 	}
78 
79 	void readFile(string commit_sha, Path path, scope void delegate(scope InputStream) reader)
80 	{
81 		assert(path.absolute, "Passed relative path to readFile.");
82 		auto url = "https://bitbucket.org/api/1.0/repositories/"~m_owner~"/"~m_project~"/raw/"~commit_sha~path.toString();
83 		downloadCached(url, (scope input) {
84 			reader(input);
85 		}, true);
86 	}
87 
88 	string getDownloadUrl(string ver)
89 	{
90 		import std.uri : encodeComponent;
91 		if( ver.startsWith("~") ) ver = ver[1 .. $];
92 		else ver = ver;
93 		return "https://bitbucket.org/"~m_owner~"/"~m_project~"/get/"~encodeComponent(ver)~".zip";
94 	}
95 
96 	void download(string ver, scope void delegate(scope InputStream) del)
97 	{
98 		downloadCached(getDownloadUrl(ver), del);
99 	}
100 }
101 
102 private auto bbToIsoDate(string bbdate)
103 {
104 	import std.array, std.datetime : SysTime;
105 	auto ttz = bbdate.split("+");
106 	if( ttz.length < 2 ) ttz ~= "00:00";
107 	auto parts = ttz[0].split("-");
108 	parts = parts[0 .. $-1] ~ parts[$-1].split(" ");
109 	parts = parts[0 .. $-1] ~ parts[$-1].split(":");
110 
111 	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]));
112 }