1/* 2 * This file is part of gitg 3 * 4 * Copyright (C) 2014 - Jesse van den Kieboom 5 * 6 * gitg is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * gitg is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with gitg. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20namespace Gitg 21{ 22 23public enum RemoteState 24{ 25 DISCONNECTED, 26 CONNECTING, 27 CONNECTED, 28 TRANSFERRING 29} 30 31public errordomain RemoteError 32{ 33 ALREADY_CONNECTED, 34 ALREADY_CONNECTING, 35 ALREADY_DISCONNECTED, 36 STILL_CONNECTING 37} 38 39public interface CredentialsProvider : Object 40{ 41 public abstract Ggit.Cred? credentials(string url, string? username_from_url, Ggit.Credtype allowed_types) throws Error; 42} 43 44public class Remote : Ggit.Remote 45{ 46 private class Callbacks : Ggit.RemoteCallbacks 47 { 48 private Remote d_remote; 49 private Ggit.RemoteCallbacks? d_proxy; 50 51 public delegate void TransferProgress(Ggit.TransferProgress stats); 52 private TransferProgress? d_transfer_progress; 53 54 public Callbacks(Remote remote, Ggit.RemoteCallbacks? proxy, owned TransferProgress? transfer_progress) 55 { 56 d_remote = remote; 57 d_proxy = proxy; 58 d_transfer_progress = (owned)transfer_progress; 59 } 60 61 protected override void progress(string message) 62 { 63 if (d_proxy != null) 64 { 65 d_proxy.progress(message); 66 } 67 } 68 69 protected override void transfer_progress(Ggit.TransferProgress stats) 70 { 71 if (d_transfer_progress != null) 72 { 73 d_transfer_progress(stats); 74 } 75 76 if (d_proxy != null) 77 { 78 d_proxy.transfer_progress(stats); 79 } 80 } 81 82 protected override void update_tips(string refname, Ggit.OId a, Ggit.OId b) 83 { 84 d_remote.tip_updated(refname, a, b); 85 86 if (d_proxy != null) 87 { 88 d_proxy.update_tips(refname, a, b); 89 } 90 } 91 92 protected override void completion(Ggit.RemoteCompletionType type) 93 { 94 if (d_proxy != null) 95 { 96 d_proxy.completion(type); 97 } 98 } 99 100 protected override Ggit.Cred? credentials(string url, string? username_from_url, Ggit.Credtype allowed_types) throws Error 101 { 102 Ggit.Cred? ret = null; 103 104 var provider = d_remote.credentials_provider; 105 106 if (provider != null) 107 { 108 ret = provider.credentials(url, username_from_url, allowed_types); 109 } 110 111 if (ret == null && d_proxy != null) 112 { 113 ret = d_proxy.credentials(url, username_from_url, allowed_types); 114 } 115 116 return ret; 117 } 118 } 119 120 private RemoteState d_state; 121 private string[]? d_fetch_specs; 122 private string[]? d_push_specs; 123 private uint d_reset_transfer_progress_timeout; 124 private double d_transfer_progress; 125 126 private Callbacks? d_callbacks; 127 128 public signal void tip_updated(string refname, Ggit.OId a, Ggit.OId b); 129 130 public override void dispose() 131 { 132 if (d_reset_transfer_progress_timeout != 0) 133 { 134 Source.remove(d_reset_transfer_progress_timeout); 135 d_reset_transfer_progress_timeout = 0; 136 } 137 138 base.dispose(); 139 } 140 141 public double transfer_progress 142 { 143 get { return d_transfer_progress; } 144 } 145 146 public RemoteState state 147 { 148 get { return d_state; } 149 private set 150 { 151 if (d_state != value) 152 { 153 d_state = value; 154 notify_property("state"); 155 } 156 } 157 } 158 159 private void do_reset_transfer_progress() 160 { 161 d_reset_transfer_progress_timeout = 0; 162 d_transfer_progress = 0.0; 163 notify_property("transfer-progress"); 164 } 165 166 private void reset_transfer_progress(bool with_delay) 167 { 168 if (d_transfer_progress == 0) 169 { 170 return; 171 } 172 173 if (with_delay) 174 { 175 d_reset_transfer_progress_timeout = Timeout.add(500, () => { 176 do_reset_transfer_progress(); 177 return false; 178 }); 179 } 180 else if (d_reset_transfer_progress_timeout == 0) 181 { 182 do_reset_transfer_progress(); 183 } 184 } 185 186 private void update_transfer_progress(Ggit.TransferProgress stats) 187 { 188 var total = stats.get_total_objects(); 189 var received = stats.get_received_objects(); 190 var indexed = stats.get_indexed_objects(); 191 192 d_transfer_progress = (double)(received + indexed) / (double)(total + total); 193 notify_property("transfer-progress"); 194 195 if (received == total && indexed == total) 196 { 197 reset_transfer_progress(true); 198 } 199 } 200 201 private void update_state(bool force_disconnect = false) 202 { 203 if (get_connected()) 204 { 205 if (force_disconnect) 206 { 207 disconnect.begin((obj, res) => { 208 try 209 { 210 disconnect.end(res); 211 } catch {} 212 }); 213 } 214 else 215 { 216 state = RemoteState.CONNECTED; 217 } 218 } 219 else 220 { 221 state = RemoteState.DISCONNECTED; 222 } 223 } 224 225 public new async void connect(Ggit.Direction direction, Ggit.RemoteCallbacks? callbacks = null) throws Error 226 { 227 if (get_connected()) 228 { 229 if (state != RemoteState.CONNECTED) 230 { 231 state = RemoteState.CONNECTED; 232 } 233 234 throw new RemoteError.ALREADY_CONNECTED("already connected"); 235 } 236 else if (state == RemoteState.CONNECTING) 237 { 238 throw new RemoteError.ALREADY_CONNECTING("already connecting"); 239 } 240 else 241 { 242 reset_transfer_progress(false); 243 } 244 245 state = RemoteState.CONNECTING; 246 247 while (true) 248 { 249 try 250 { 251 d_callbacks = new Callbacks(this, callbacks, update_transfer_progress); 252 253 yield Async.thread(() => { 254 base.connect(direction, d_callbacks, null, null); 255 }); 256 } 257 catch (Error e) 258 { 259 d_callbacks = null; 260 261 // NOTE: need to check the message for now in case of failed 262 // http or ssh auth. This is fragile and will likely break 263 // in future libgit2 releases. Please fix! 264 if (e.message == "Unexpected HTTP status code: 401" || 265 e.message == "error authenticating: Username/PublicKey combination invalid") 266 { 267 continue; 268 } 269 else 270 { 271 update_state(); 272 throw e; 273 } 274 } 275 276 break; 277 } 278 279 update_state(); 280 } 281 282 public new async void disconnect() throws Error 283 { 284 if (!get_connected()) 285 { 286 if (state != RemoteState.DISCONNECTED) 287 { 288 state = RemoteState.DISCONNECTED; 289 } 290 291 throw new RemoteError.ALREADY_DISCONNECTED("already disconnected"); 292 } 293 294 try 295 { 296 yield Async.thread(() => { 297 base.disconnect(); 298 }); 299 } 300 catch (Error e) 301 { 302 update_state(); 303 reset_transfer_progress(true); 304 305 throw e; 306 } 307 308 update_state(); 309 reset_transfer_progress(true); 310 } 311 312 private async void push_intern(string branch, Ggit.RemoteCallbacks? callbacks) throws Error 313 { 314 state = RemoteState.TRANSFERRING; 315 reset_transfer_progress(false); 316 317 try 318 { 319 yield Async.thread(() => { 320 var options = new Ggit.PushOptions(); 321 if (d_callbacks == null) { 322 d_callbacks = new Callbacks(this, callbacks, update_transfer_progress); 323 } 324 325 options.set_remote_callbacks(d_callbacks); 326 327 string [] push_refs = { "refs/heads/%s:refs/heads/%s".printf(branch, branch) }; 328 329 if (!base.push(push_refs, options)) 330 throw new Error(0,0,"push"); 331 }); 332 } 333 catch (Error e) 334 { 335 reset_transfer_progress(true); 336 throw e; 337 } 338 339 reset_transfer_progress(true); 340 } 341 342 private async void download_intern(string? message, Ggit.RemoteCallbacks? callbacks) throws Error 343 { 344 bool dis = false; 345 346 if (!get_connected()) 347 { 348 dis = true; 349 yield connect(Ggit.Direction.FETCH, callbacks); 350 } 351 352 state = RemoteState.TRANSFERRING; 353 reset_transfer_progress(false); 354 355 try 356 { 357 yield Async.thread(() => { 358 var options = new Ggit.FetchOptions(); 359 options.set_remote_callbacks(d_callbacks); 360 361 base.download(null, options); 362 363 if (message != null) 364 { 365 base.update_tips(d_callbacks, true, options.get_download_tags(), message); 366 } 367 }); 368 } 369 catch (Error e) 370 { 371 update_state(dis); 372 reset_transfer_progress(true); 373 throw e; 374 } 375 376 update_state(dis); 377 reset_transfer_progress(true); 378 } 379 380 public new async void download(Ggit.RemoteCallbacks? callbacks = null) throws Error 381 { 382 yield download_intern(null, callbacks); 383 } 384 385 public new async void push(string branch, Ggit.RemoteCallbacks? callbacks = null) throws Error 386 { 387 yield push_intern(branch, callbacks); 388 } 389 390 public new async void fetch(string? message, Ggit.RemoteCallbacks? callbacks = null) throws Error 391 { 392 var msg = message; 393 394 if (msg == null) 395 { 396 var name = get_name(); 397 398 if (name == null) 399 { 400 name = get_url(); 401 } 402 403 if (name != null) 404 { 405 msg = "fetch: " + name; 406 } 407 else 408 { 409 msg = ""; 410 } 411 } 412 413 yield download_intern(msg, callbacks); 414 } 415 416 public string[]? fetch_specs 417 { 418 owned get 419 { 420 if (d_fetch_specs != null) 421 { 422 return d_fetch_specs; 423 } 424 425 try 426 { 427 return get_fetch_specs(); 428 } 429 catch (Error e) 430 { 431 return null; 432 } 433 } 434 435 set 436 { 437 d_fetch_specs = value; 438 } 439 } 440 441 public string[]? push_specs 442 { 443 owned get 444 { 445 if (d_push_specs != null) 446 { 447 return d_push_specs; 448 } 449 450 try 451 { 452 return get_push_specs(); 453 } 454 catch (Error e) 455 { 456 return null; 457 } 458 } 459 460 set 461 { 462 d_push_specs = value; 463 } 464 } 465 466 public CredentialsProvider? credentials_provider 467 { 468 get; set; 469 } 470} 471 472} 473