gitg/libgitg/gitg-remote.vala
Jesse van den Kieboom 8786f4f068 Rework the dash view implementation
- Make the dash view more self contained
- Move the open and clone menu options in an action bar
- Add cloning authentication/credentials
2015-08-04 17:35:16 +02:00

433 lines
8 KiB
Vala

/*
* This file is part of gitg
*
* Copyright (C) 2014 - Jesse van den Kieboom
*
* gitg is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* gitg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gitg. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Gitg
{
public enum RemoteState
{
DISCONNECTED,
CONNECTING,
CONNECTED,
TRANSFERRING
}
public errordomain RemoteError
{
ALREADY_CONNECTED,
ALREADY_CONNECTING,
ALREADY_DISCONNECTED,
STILL_CONNECTING
}
public interface CredentialsProvider : Object
{
public abstract Ggit.Cred? credentials(string url, string? username_from_url, Ggit.Credtype allowed_types) throws Error;
}
public class Remote : Ggit.Remote
{
private class Callbacks : Ggit.RemoteCallbacks
{
private Remote d_remote;
private Ggit.RemoteCallbacks? d_proxy;
public delegate void TransferProgress(Ggit.TransferProgress stats);
private TransferProgress? d_transfer_progress;
public Callbacks(Remote remote, Ggit.RemoteCallbacks? proxy, owned TransferProgress? transfer_progress)
{
d_remote = remote;
d_proxy = proxy;
d_transfer_progress = (owned)transfer_progress;
}
protected override void progress(string message)
{
if (d_proxy != null)
{
d_proxy.progress(message);
}
}
protected override void transfer_progress(Ggit.TransferProgress stats)
{
if (d_transfer_progress != null)
{
d_transfer_progress(stats);
}
if (d_proxy != null)
{
d_proxy.transfer_progress(stats);
}
}
protected override void update_tips(string refname, Ggit.OId a, Ggit.OId b)
{
d_remote.tip_updated(refname, a, b);
if (d_proxy != null)
{
d_proxy.update_tips(refname, a, b);
}
}
protected override void completion(Ggit.RemoteCompletionType type)
{
if (d_proxy != null)
{
d_proxy.completion(type);
}
}
protected override Ggit.Cred? credentials(string url, string? username_from_url, Ggit.Credtype allowed_types) throws Error
{
Ggit.Cred? ret = null;
var provider = d_remote.credentials_provider;
if (provider != null)
{
ret = provider.credentials(url, username_from_url, allowed_types);
}
if (ret == null && d_proxy != null)
{
ret = d_proxy.credentials(url, username_from_url, allowed_types);
}
return ret;
}
}
private RemoteState d_state;
private string[]? d_fetch_specs;
private string[]? d_push_specs;
private uint d_reset_transfer_progress_timeout;
private double d_transfer_progress;
public signal void tip_updated(string refname, Ggit.OId a, Ggit.OId b);
public override void dispose()
{
if (d_reset_transfer_progress_timeout != 0)
{
Source.remove(d_reset_transfer_progress_timeout);
d_reset_transfer_progress_timeout = 0;
}
base.dispose();
}
public double transfer_progress
{
get { return d_transfer_progress; }
}
public RemoteState state
{
get { return d_state; }
private set
{
if (d_state != value)
{
d_state = value;
notify_property("state");
}
}
}
private void do_reset_transfer_progress()
{
d_reset_transfer_progress_timeout = 0;
d_transfer_progress = 0.0;
notify_property("transfer-progress");
}
private void reset_transfer_progress(bool with_delay)
{
if (d_transfer_progress == 0)
{
return;
}
if (with_delay)
{
d_reset_transfer_progress_timeout = Timeout.add(500, () => {
do_reset_transfer_progress();
return false;
});
}
else if (d_reset_transfer_progress_timeout == 0)
{
do_reset_transfer_progress();
}
}
private void update_transfer_progress(Ggit.TransferProgress stats)
{
var total = stats.get_total_objects();
var received = stats.get_received_objects();
var indexed = stats.get_indexed_objects();
d_transfer_progress = (double)(received + indexed) / (double)(total + total);
notify_property("transfer-progress");
if (received == total && indexed == total)
{
reset_transfer_progress(true);
}
}
private void update_state(bool force_disconnect = false)
{
if (get_connected())
{
if (force_disconnect)
{
disconnect.begin((obj, res) => {
try
{
disconnect.end(res);
} catch {}
});
}
else
{
state = RemoteState.CONNECTED;
}
}
else
{
state = RemoteState.DISCONNECTED;
}
}
public new async void connect(Ggit.Direction direction, Ggit.RemoteCallbacks? callbacks = null) throws Error
{
if (get_connected())
{
if (state != RemoteState.CONNECTED)
{
state = RemoteState.CONNECTED;
}
throw new RemoteError.ALREADY_CONNECTED("already connected");
}
else if (state == RemoteState.CONNECTING)
{
throw new RemoteError.ALREADY_CONNECTING("already connecting");
}
else
{
reset_transfer_progress(false);
}
state = RemoteState.CONNECTING;
while (true)
{
try
{
yield Async.thread(() => {
base.connect(direction, new Callbacks(this, callbacks, null));
});
}
catch (Error e)
{
// NOTE: need to check the message for now in case of failed
// http or ssh auth. This is fragile and will likely break
// in future libgit2 releases. Please fix!
if (e.message == "Unexpected HTTP status code: 401" ||
e.message == "error authenticating: Username/PublicKey combination invalid")
{
continue;
}
else
{
update_state();
throw e;
}
}
break;
}
update_state();
}
public new async void disconnect() throws Error
{
if (!get_connected())
{
if (state != RemoteState.DISCONNECTED)
{
state = RemoteState.DISCONNECTED;
}
throw new RemoteError.ALREADY_DISCONNECTED("already disconnected");
}
try
{
yield Async.thread(() => {
base.disconnect();
});
}
catch (Error e)
{
update_state();
reset_transfer_progress(true);
throw e;
}
update_state();
reset_transfer_progress(true);
}
private async void download_intern(string? message, Ggit.RemoteCallbacks? callbacks) throws Error
{
bool dis = false;
if (!get_connected())
{
dis = true;
yield connect(Ggit.Direction.FETCH, callbacks);
}
state = RemoteState.TRANSFERRING;
reset_transfer_progress(false);
try
{
yield Async.thread(() => {
var options = new Ggit.FetchOptions();
var cbs = new Callbacks(this, callbacks, update_transfer_progress);
options.set_remote_callbacks(cbs);
base.download(null, options);
if (message != null)
{
base.update_tips(cbs, true, options.get_download_tags(), message);
}
});
}
catch (Error e)
{
update_state(dis);
reset_transfer_progress(true);
throw e;
}
update_state(dis);
reset_transfer_progress(true);
}
public new async void download(Ggit.RemoteCallbacks? callbacks = null) throws Error
{
yield download_intern(null, callbacks);
}
public new async void fetch(string? message, Ggit.RemoteCallbacks? callbacks = null) throws Error
{
var msg = message;
if (msg == null)
{
var name = get_name();
if (name == null)
{
name = get_url();
}
if (name != null)
{
msg = "fetch: " + name;
}
else
{
msg = "";
}
}
yield download_intern(msg, callbacks);
}
public string[]? fetch_specs
{
owned get
{
if (d_fetch_specs != null)
{
return d_fetch_specs;
}
try
{
return get_fetch_specs();
}
catch (Error e)
{
return null;
}
}
set
{
d_fetch_specs = value;
}
}
public string[]? push_specs
{
owned get
{
if (d_push_specs != null)
{
return d_push_specs;
}
try
{
return get_push_specs();
}
catch (Error e)
{
return null;
}
}
set
{
d_push_specs = value;
}
}
public CredentialsProvider? credentials_provider
{
get; set;
}
}
}