gnostr_asyncgit/sync/remotes/
callbacks.rs1use std::sync::{
2 Arc, Mutex,
3 atomic::{AtomicBool, Ordering},
4};
5
6use crossbeam_channel::Sender;
7use git2::{Cred, Error as GitError, RemoteCallbacks};
8
9use super::push::ProgressNotification;
10use crate::{error::Result, sync::cred::BasicAuthCredential};
11
12#[derive(Default, Clone)]
14pub struct CallbackStats {
15 pub push_rejected_msg: Option<(String, String)>,
16}
17
18#[derive(Clone)]
20pub struct Callbacks {
21 sender: Option<Sender<ProgressNotification>>,
22 basic_credential: Option<BasicAuthCredential>,
23 stats: Arc<Mutex<CallbackStats>>,
24 first_call_to_credentials: Arc<AtomicBool>,
25}
26
27impl Callbacks {
28 pub fn new(
30 sender: Option<Sender<ProgressNotification>>,
31 basic_credential: Option<BasicAuthCredential>,
32 ) -> Self {
33 let stats = Arc::new(Mutex::new(CallbackStats::default()));
34
35 Self {
36 sender,
37 basic_credential,
38 stats,
39 first_call_to_credentials: Arc::new(AtomicBool::new(
40 true,
41 )),
42 }
43 }
44
45 pub fn get_stats(&self) -> Result<CallbackStats> {
47 let stats = self.stats.lock()?;
48 Ok(stats.clone())
49 }
50
51 pub fn callbacks<'a>(&self) -> RemoteCallbacks<'a> {
53 let mut callbacks = RemoteCallbacks::new();
54
55 let this = self.clone();
56 callbacks.push_transfer_progress(
57 move |current, total, bytes| {
58 this.push_transfer_progress(current, total, bytes);
59 },
60 );
61
62 let this = self.clone();
63 callbacks.update_tips(move |name, a, b| {
64 this.update_tips(name, a, b);
65 true
66 });
67
68 let this = self.clone();
69 callbacks.transfer_progress(move |p| {
70 this.transfer_progress(&p);
71 true
72 });
73
74 let this = self.clone();
75 callbacks.pack_progress(move |stage, current, total| {
76 this.pack_progress(stage, total, current);
77 });
78
79 let this = self.clone();
80 callbacks.push_update_reference(move |reference, msg| {
81 this.push_update_reference(reference, msg);
82 Ok(())
83 });
84
85 let this = self.clone();
86 callbacks.credentials(
87 move |url, username_from_url, allowed_types| {
88 this.credentials(
89 url,
90 username_from_url,
91 allowed_types,
92 )
93 },
94 );
95
96 callbacks.sideband_progress(move |data| {
97 log::debug!(
98 "sideband transfer: '{}'",
99 String::from_utf8_lossy(data).trim()
100 );
101 true
102 });
103
104 callbacks
105 }
106
107 fn push_update_reference(
108 &self,
109 reference: &str,
110 msg: Option<&str>,
111 ) {
112 log::debug!(
113 "push_update_reference: '{}' {:?}",
114 reference,
115 msg
116 );
117
118 if let Ok(mut stats) = self.stats.lock() {
119 stats.push_rejected_msg = msg
120 .map(|msg| (reference.to_string(), msg.to_string()));
121 }
122 }
123
124 fn pack_progress(
125 &self,
126 stage: git2::PackBuilderStage,
127 total: usize,
128 current: usize,
129 ) {
130 log::debug!("packing: {:?} - {}/{}", stage, current, total);
131 self.sender.clone().map(|sender| {
132 sender.send(ProgressNotification::Packing {
133 stage,
134 total,
135 current,
136 })
137 });
138 }
139
140 fn transfer_progress(&self, p: &git2::Progress) {
141 log::debug!(
142 "transfer: {}/{}",
143 p.received_objects(),
144 p.total_objects()
145 );
146 self.sender.clone().map(|sender| {
147 sender.send(ProgressNotification::Transfer {
148 objects: p.received_objects(),
149 total_objects: p.total_objects(),
150 })
151 });
152 }
153
154 fn update_tips(&self, name: &str, a: git2::Oid, b: git2::Oid) {
155 log::debug!("update tips: '{}' [{}] [{}]", name, a, b);
156 self.sender.clone().map(|sender| {
157 sender.send(ProgressNotification::UpdateTips {
158 name: name.to_string(),
159 a: a.into(),
160 b: b.into(),
161 })
162 });
163 }
164
165 fn push_transfer_progress(
166 &self,
167 current: usize,
168 total: usize,
169 bytes: usize,
170 ) {
171 log::debug!("progress: {}/{} ({} B)", current, total, bytes,);
172 self.sender.clone().map(|sender| {
173 sender.send(ProgressNotification::PushTransfer {
174 current,
175 total,
176 bytes,
177 })
178 });
179 }
180
181 fn credentials(
187 &self,
188 url: &str,
189 username_from_url: Option<&str>,
190 allowed_types: git2::CredentialType,
191 ) -> std::result::Result<Cred, GitError> {
192 log::debug!(
193 "creds: '{}' {:?} ({:?})",
194 url,
195 username_from_url,
196 allowed_types
197 );
198
199 if self.first_call_to_credentials.load(Ordering::Relaxed) {
202 self.first_call_to_credentials
203 .store(false, Ordering::Relaxed);
204 } else {
205 return Err(GitError::from_str("Bad credentials."));
206 }
207
208 match &self.basic_credential {
209 _ if allowed_types.is_ssh_key() => username_from_url
210 .map_or_else(
211 || {
212 Err(GitError::from_str(
213 " Couldn't extract username from url.",
214 ))
215 },
216 Cred::ssh_key_from_agent,
217 ),
218 Some(BasicAuthCredential {
219 username: Some(user),
220 password: Some(pwd),
221 }) if allowed_types.is_user_pass_plaintext() => {
222 Cred::userpass_plaintext(user, pwd)
223 }
224 Some(BasicAuthCredential {
225 username: Some(user),
226 password: _,
227 }) if allowed_types.is_username() => Cred::username(user),
228 _ if allowed_types.is_default() => Cred::default(),
229 _ => Err(GitError::from_str("Couldn't find credentials")),
230 }
231 }
232}