blob: eb600badf323a9c0d3f475e4d38cce59b1749a7b [file] [log] [blame]
Han-Wen Nienhuys4bf7fcc2016-06-02 15:03:59 +02001// Copyright 2016 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Package gitiles is a client library for the Gitiles source viewer.
16package gitiles
17
18import (
19 "bytes"
20 "encoding/base64"
21 "encoding/json"
22 "fmt"
23 "io/ioutil"
24 "log"
25 "net/http"
26 "net/url"
27 "path"
28)
29
30// Service is a client for the Gitiles JSON interface.
31type Service struct {
32 throttle chan struct{}
33 addr url.URL
34}
35
36// Addr returns the address of the gitiles service.
37func (s *Service) Addr() string {
38 return s.addr.String()
39}
40
41// NewService returns a new Gitiles JSON client.
42func NewService(addr string) (*Service, error) {
43 url, err := url.Parse(addr)
44 if err != nil {
45 return nil, err
46 }
47 return &Service{
48 throttle: make(chan struct{}, 12),
49 addr: *url,
50 }, nil
51}
52
53func (s *Service) get(u url.URL) ([]byte, error) {
54 s.throttle <- struct{}{}
55 resp, err := http.Get(u.String())
56 <-s.throttle
57 if err != nil {
58 return nil, err
59 }
60 defer resp.Body.Close()
61 if resp.StatusCode != 200 {
Han-Wen Nienhuys39ace572016-06-02 18:30:51 +020062 return nil, fmt.Errorf("%s: %s", u.String(), resp.Status)
Han-Wen Nienhuys4bf7fcc2016-06-02 15:03:59 +020063 }
64
65 c, err := ioutil.ReadAll(resp.Body)
66 if err != nil {
67 return nil, err
68 }
69 return c, nil
70}
71
72var xssTag = []byte(")]}'\n")
73
74func (s *Service) getJSON(u url.URL, dest interface{}) error {
75 c, err := s.get(u)
76 if err != nil {
77 return err
78 }
79
80 if !bytes.HasPrefix(c, xssTag) {
81 return fmt.Errorf("Gitiles JSON %s missing XSS tag: %q", u, c)
82 }
83 c = c[len(xssTag):]
84
85 err = json.Unmarshal(c, dest)
86 if err != nil {
87 err = fmt.Errorf("Unmarshal(%s): %v", u, err)
88 }
89 return err
90}
91
92// List retrieves the list of projects.
93func (s *Service) List() (map[string]*Project, error) {
94 listURL := s.addr
95 listURL.RawQuery = "format=JSON"
96
97 projects := map[string]*Project{}
98 err := s.getJSON(listURL, &projects)
99 return projects, err
100}
101
Han-Wen Nienhuys39ace572016-06-02 18:30:51 +0200102func (s *Service) NewRepoService(name string) *RepoService {
103 return &RepoService{
104 Name: name,
105 service: s,
106 }
107}
108
Han-Wen Nienhuys4bf7fcc2016-06-02 15:03:59 +0200109// RepoService is a JSON client for the functionality of a specific
110// respository.
111type RepoService struct {
112 Name string
113 service *Service
114}
115
Han-Wen Nienhuys27dde402016-06-03 17:44:33 +0200116// Get retrieves a single project.
117func (s *RepoService) Get() (*Project, error) {
118 jsonURL := s.service.addr
119 jsonURL.Path = path.Join(jsonURL.Path, s.Name)
120 jsonURL.RawQuery = "format=JSON"
121
122 var p Project
123 err := s.service.getJSON(jsonURL, &p)
124 return &p, err
125}
126
Han-Wen Nienhuys4bf7fcc2016-06-02 15:03:59 +0200127// GetBlob fetches a blob.
128func (s *RepoService) GetBlob(branch, filename string) ([]byte, error) {
129 blobURL := s.service.addr
130
131 blobURL.Path = path.Join(blobURL.Path, s.Name, "+show", branch, filename)
132 blobURL.RawQuery = "format=TEXT"
133
134 // TODO(hanwen): invent a more structured mechanism for logging.
135 log.Println(blobURL.String())
136 c, err := s.service.get(blobURL)
137 if err != nil {
138 return nil, err
139 }
140
141 out := make([]byte, base64.StdEncoding.DecodedLen(len(c)))
142 n, err := base64.StdEncoding.Decode(out, c)
143
144 return out[:n], err
145}
146
147// GetTree fetches a tree. The dir argument may not point to a
148// blob. If recursive is given, the server recursively expands the
149// tree.
150func (s *RepoService) GetTree(branch, dir string, recursive bool) (*Tree, error) {
151 jsonURL := s.service.addr
152 jsonURL.Path = path.Join(jsonURL.Path, s.Name, "+", branch, dir)
153 if dir == "" {
154 jsonURL.Path += "/"
155 }
156 jsonURL.RawQuery = "format=JSON&long=1"
157
158 if recursive {
159 jsonURL.RawQuery += "&recursive=1"
160 }
161
162 var tree Tree
163 err := s.service.getJSON(jsonURL, &tree)
164 return &tree, err
165}
166
167// GetCommit gets the data of a commit in a branch.
168func (s *RepoService) GetCommit(branch string) (*Commit, error) {
169 jsonURL := s.service.addr
170 jsonURL.Path = path.Join(jsonURL.Path, s.Name, "+", branch)
171 jsonURL.RawQuery = "format=JSON"
172
173 var c Commit
174 err := s.service.getJSON(jsonURL, &c)
175 return &c, err
176}