| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 import 'dart:dartino'; | |
| 6 import 'dart:convert' show JSON, UTF8; | |
| 7 | |
| 8 import 'package:http/http.dart'; | |
| 9 import 'package:socket/socket.dart'; | |
| 10 | |
| 11 abstract class Connection { | |
| 12 final String host; | |
| 13 final int port; | |
| 14 Socket connect(); | |
| 15 void close(); | |
| 16 } | |
| 17 | |
| 18 class _ConnectionImpl implements Connection { | |
| 19 final String host; | |
| 20 final int port; | |
| 21 _ConnectionImpl(this.host, this.port); | |
| 22 Socket connect() => new Socket.connect(host, port); | |
| 23 void close() {} | |
| 24 } | |
| 25 | |
| 26 class _ConnectionInvertedImpl implements Connection { | |
| 27 final String host = '127.0.0.1'; | |
| 28 final int port; | |
| 29 ServerSocket _socket; | |
| 30 _ConnectionInvertedImpl(this.port) { | |
| 31 _socket = new ServerSocket(host, port); | |
| 32 print('Waiting for Github mock server on $host:$port'); | |
| 33 connect(); | |
| 34 } | |
| 35 Socket connect() => _socket.accept(); | |
| 36 void close() { _socket.close(); } | |
| 37 } | |
| 38 | |
| 39 getJson(Connection service, String resource) { | |
| 40 HttpConnection connection = new HttpConnection(service.connect()); | |
| 41 HttpRequest request = new HttpRequest('${service.host}/$resource'); | |
| 42 request.headers["Host"] = service.host; | |
| 43 request.headers["User-Agent"] = 'dartino'; | |
| 44 HttpResponse response = connection.send(request); | |
| 45 if (response.statusCode != 200) { | |
| 46 throw 'Failed request: $resource on port ${service.port}'; | |
| 47 } | |
| 48 return JSON.decode(UTF8.decode(response.body)); | |
| 49 } | |
| 50 | |
| 51 class Server { | |
| 52 static const githubApiUrl = 'https://api.github.com'; | |
| 53 String get host => _connection.host; | |
| 54 int get port => _connection.port; | |
| 55 | |
| 56 Connection _connection; | |
| 57 | |
| 58 List<Channel> outstanding = []; | |
| 59 | |
| 60 Server(String host, int port) { | |
| 61 _connection = new _ConnectionImpl(host, port); | |
| 62 } | |
| 63 | |
| 64 Server.invertedForTesting(int port) { | |
| 65 _connection = new _ConnectionInvertedImpl(port); | |
| 66 } | |
| 67 | |
| 68 void close() { | |
| 69 var local = outstanding; | |
| 70 outstanding = []; | |
| 71 for (Channel channel in local) channel.receive(); | |
| 72 _connection.close(); | |
| 73 } | |
| 74 | |
| 75 dynamic get(String resource) => getJson(_connection, resource); | |
| 76 | |
| 77 User getUser(String name) => new User(name, this); | |
| 78 } | |
| 79 | |
| 80 abstract class Service { | |
| 81 final String api; | |
| 82 final Server server; | |
| 83 | |
| 84 var _data = null; | |
| 85 | |
| 86 Service(this.api, this.server); | |
| 87 | |
| 88 dynamic operator[](String key) { | |
| 89 _ensureData(); | |
| 90 return _data[key]; | |
| 91 } | |
| 92 | |
| 93 void _ensureData() { | |
| 94 if (_data != null) return; | |
| 95 _data = server.get(api); | |
| 96 } | |
| 97 } | |
| 98 | |
| 99 class User extends Service { | |
| 100 final String name; | |
| 101 | |
| 102 User(String name, Server parent) | |
| 103 : super('users/$name', parent), | |
| 104 this.name = name; | |
| 105 | |
| 106 Repository getRepository(String repo) => new Repository(repo, this); | |
| 107 } | |
| 108 | |
| 109 class Repository extends Service { | |
| 110 final String name; | |
| 111 final Pagination _commitPages; | |
| 112 | |
| 113 Repository(String name, User parent) | |
| 114 : super('repos/${parent.name}/$name', parent.server), | |
| 115 this.name = name, | |
| 116 _commitPages = new Pagination( | |
| 117 'repos/${parent.name}/$name/commits', parent.server); | |
| 118 | |
| 119 dynamic getCommitAt(int index) => _commitPages.itemAt(index); | |
| 120 | |
| 121 void prefetchCommitsInRange(int start, int end) => | |
| 122 _commitPages.prefetchItemsInRange(start, end); | |
| 123 } | |
| 124 | |
| 125 class Pagination { | |
| 126 static const count = 30; | |
| 127 | |
| 128 final Server server; | |
| 129 final String api; | |
| 130 List _pages = []; | |
| 131 | |
| 132 Pagination(this.api, this.server); | |
| 133 | |
| 134 void prefetch(int page) { | |
| 135 _fetch(page, true); | |
| 136 } | |
| 137 | |
| 138 List fetch(int page) { | |
| 139 return _fetch(page, false); | |
| 140 } | |
| 141 | |
| 142 void prefetchItemsInRange(int start, int end) { | |
| 143 int firstPage = start ~/ count; | |
| 144 int lastPage = (end ~/ count) + 1; | |
| 145 // Scheduling prefetching of surrounding pages in descending order. | |
| 146 // (Currently the dartino scheduler will process these in reverse order). | |
| 147 if (firstPage > 0) prefetch(firstPage - 1); | |
| 148 prefetch(lastPage + 1); | |
| 149 for (int i = lastPage; i >= firstPage; --i) { | |
| 150 prefetch(i); | |
| 151 } | |
| 152 } | |
| 153 | |
| 154 dynamic itemAt(int index) { | |
| 155 int page = index ~/ count; | |
| 156 int entry = index % count; | |
| 157 List entries = fetch(page); | |
| 158 return (entries != null && entry < entries.length) ? entries[entry] : null; | |
| 159 } | |
| 160 | |
| 161 _fetch(int page, bool prefetch) { | |
| 162 var entries = null; | |
| 163 if (_pages.length <= page) { | |
| 164 _pages.length = page + 1; | |
| 165 } else { | |
| 166 entries = _pages[page]; | |
| 167 } | |
| 168 if (entries is Channel) { | |
| 169 if (prefetch) return; | |
| 170 return entries.receive(); | |
| 171 } | |
| 172 if (entries == null) { | |
| 173 Channel channel = new Channel(); | |
| 174 _pages[page] = channel; | |
| 175 if (prefetch) { | |
| 176 server.outstanding.add(channel); | |
| 177 Fiber.fork(() { | |
| 178 _doFetch(channel, page); | |
| 179 server.outstanding.remove(channel); | |
| 180 }); | |
| 181 } else { | |
| 182 return _doFetch(channel, page); | |
| 183 } | |
| 184 } | |
| 185 return entries; | |
| 186 } | |
| 187 | |
| 188 List _doFetch(Channel channel, int page) { | |
| 189 List entries = null; | |
| 190 try { | |
| 191 entries = server.get('$api?page=${page + 1}'); | |
| 192 } catch (_) { | |
| 193 // Throws once when we hit past the last page. | |
| 194 } | |
| 195 _pages[page] = entries; | |
| 196 channel.send(entries); | |
| 197 return entries; | |
| 198 } | |
| 199 } | |
| OLD | NEW |