summary refs log tree commit diff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2020-08-31 11:36:26 +0200
committerLudovic Courtès <ludo@gnu.org>2020-08-31 15:50:31 +0200
commitb03267df6d5ec44e9617b6aab0df14a2e79f822e (patch)
treedd7d8a5ba571bc19d80a2997cf0948d621bb5710
parent7ae04561660ea06c4478d8fb08e895a4008307d0 (diff)
downloadguix-b03267df6d5ec44e9617b6aab0df14a2e79f822e.tar.gz
ssh: 'send-files' displays a progress bar.
* guix/store.scm (export-paths): Add #:start, #:progress, and #:finish
parameters and honor them.
* guix/ssh.scm (prepare-to-send, notify-transfer-progress)
(notify-transfer-completion): New procedures.
(send-files): Pass #:start, #:progress, and #:finish to 'export-paths'.
-rw-r--r--guix/ssh.scm77
-rw-r--r--guix/store.scm24
2 files changed, 83 insertions, 18 deletions
diff --git a/guix/ssh.scm b/guix/ssh.scm
index 24db171374..5f94528520 100644
--- a/guix/ssh.scm
+++ b/guix/ssh.scm
@@ -20,7 +20,11 @@
   #:use-module (guix store)
   #:use-module (guix inferior)
   #:use-module (guix i18n)
-  #:use-module ((guix diagnostics) #:select (&fix-hint formatted-message))
+  #:use-module ((guix diagnostics)
+                #:select (info &fix-hint formatted-message))
+  #:use-module ((guix progress)
+                #:select (progress-bar
+                          erase-current-line current-terminal-columns))
   #:use-module (gcrypt pk-crypto)
   #:use-module (ssh session)
   #:use-module (ssh auth)
@@ -36,6 +40,7 @@
   #:use-module (ice-9 match)
   #:use-module (ice-9 format)
   #:use-module (ice-9 binary-ports)
+  #:use-module (ice-9 vlist)
   #:export (open-ssh-session
             authenticate-server*
 
@@ -402,6 +407,55 @@ to the system ACL file if it has not yet been authorized."
    session
    become-command))
 
+(define (prepare-to-send store host log-port items)
+  "Notify the user that we're about to send ITEMS to HOST.  Return three
+values allowing 'notify-send-progress' to track the state of this transfer."
+  (let* ((count (length items))
+         (sizes (fold (lambda (item result)
+                        (vhash-cons item
+                                    (path-info-nar-size
+                                     (query-path-info store item))
+                                    result))
+                      vlist-null
+                      items))
+         (total  (vlist-fold (lambda (pair result)
+                               (match pair
+                                 ((_ . size) (+ size result))))
+                             0
+                             sizes)))
+    (info (N_ "sending ~a store item (~h MiB) to '~a'...~%"
+              "sending ~a store items (~h MiB) to '~a'...~%" count)
+          count
+          (inexact->exact (round (/ total (expt 2. 20))))
+          host)
+
+    (values log-port sizes total 0)))
+
+(define (notify-transfer-progress item port sizes total sent)
+  "Notify the user that we've already transferred SENT bytes out of TOTAL.
+Use SIZES to determine the size of ITEM, which is about to be sent."
+  (define (display-bar %)
+    (erase-current-line port)
+    (format port "~3@a% ~a"
+            (inexact->exact (round (* 100. (/ sent total))))
+            (progress-bar % (- (max (current-terminal-columns) 5) 5)))
+    (force-output port))
+
+  (let ((% (* 100. (/ sent total))))
+    (match (vhash-assoc item sizes)
+      (#f
+       (display-bar %)
+       (values port sizes total sent))
+      ((_ . size)
+       (display-bar %)
+       (values port sizes total (+ sent size))))))
+
+(define (notify-transfer-completion port . args)
+  "Notify the user that the transfer has completed."
+  (apply notify-transfer-progress "" port args) ;display the 100% progress bar
+  (erase-current-line port)
+  (force-output port))
+
 (define* (send-files local files remote
                      #:key
                      recursive?
@@ -412,7 +466,7 @@ Return the list of store items actually sent."
   ;; Compute the subset of FILES missing on SESSION and send them.
   (let* ((files   (if recursive? (requisites local files) files))
          (session (channel-get-session (store-connection-socket remote)))
-         (missing (inferior-remote-eval
+         (missing (take files 20) #;(inferior-remote-eval
                    `(begin
                       (use-modules (guix)
                                    (srfi srfi-1) (srfi srfi-26))
@@ -421,11 +475,8 @@ Return the list of store items actually sent."
                         (remove (cut valid-path? store <>)
                                 ',files)))
                    session))
-         (count   (length missing))
-         (sizes   (map (lambda (item)
-                         (path-info-nar-size (query-path-info local item)))
-                       missing))
-         (port    (store-import-channel session)))
+         (port    (store-import-channel session))
+         (host    (session-get session 'host)))
     ;; Make sure everything alright on the remote side.
     (match (read port)
       (('importing)
@@ -433,14 +484,12 @@ Return the list of store items actually sent."
       (sexp
        (handle-import/export-channel-error sexp remote)))
 
-    (format log-port (N_ "sending ~a store item (~h MiB) to '~a'...~%"
-                         "sending ~a store items (~h MiB) to '~a'...~%" count)
-            count
-            (inexact->exact (round (/ (reduce + 0 sizes) (expt 2. 20))))
-            (session-get session 'host))
-
     ;; Send MISSING in topological order.
-    (export-paths local missing port)
+    (let ((tty? (isatty? log-port)))
+      (export-paths local missing port
+                    #:start (cut prepare-to-send local host log-port <>)
+                    #:progress (if tty? notify-transfer-progress (const #f))
+                    #:finish (if tty? notify-transfer-completion (const #f))))
 
     ;; Tell the remote process that we're done.  (In theory the end-of-archive
     ;; mark of 'export-paths' would be enough, but in practice it's not.)
diff --git a/guix/store.scm b/guix/store.scm
index 495dc1692c..6bb6f43f56 100644
--- a/guix/store.scm
+++ b/guix/store.scm
@@ -1728,10 +1728,20 @@ is raised if the set of paths read from PORT is not signed (as per
       (or done? (loop (process-stderr server port))))
     (= 1 (read-int s))))
 
-(define* (export-paths server paths port #:key (sign? #t) recursive?)
+(define* (export-paths server paths port #:key (sign? #t) recursive?
+                       (start (const #f))
+                       (progress (const #f))
+                       (finish (const #f)))
   "Export the store paths listed in PATHS to PORT, in topological order,
 signing them if SIGN? is true.  When RECURSIVE? is true, export the closure of
-PATHS---i.e., PATHS and all their dependencies."
+PATHS---i.e., PATHS and all their dependencies.
+
+START, PROGRESS, and FINISH are used to track progress of the data transfer.
+START is a one-argument that is passed the list of store items that will be
+transferred; it returns values that are then used as the initial state
+threaded through PROGRESS calls.  PROGRESS is passed the store item about to
+be sent, along with the values previously return by START or by PROGRESS
+itself.  FINISH is called when the last store item has been called."
   (define ordered
     (let ((sorted (topologically-sorted server paths)))
       ;; When RECURSIVE? is #f, filter out the references of PATHS.
@@ -1739,14 +1749,20 @@ PATHS---i.e., PATHS and all their dependencies."
           sorted
           (filter (cut member <> paths) sorted))))
 
-  (let loop ((paths ordered))
+  (let loop ((paths ordered)
+             (state (call-with-values (lambda () (start ordered))
+                      list)))
     (match paths
       (()
+       (apply finish state)
        (write-int 0 port))
       ((head tail ...)
        (write-int 1 port)
        (and (export-path server head port #:sign? sign?)
-            (loop tail))))))
+            (loop tail
+                  (call-with-values
+                      (lambda () (apply progress head state))
+                    list)))))))
 
 (define-operation (query-failed-paths)
   "Return the list of store items for which a build failure is cached.