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 }