24.07.2011 21:37, Diogo Sousa пишет:
Hi,

One feature that some users search for is a progress bar on the cp program.

I would like to implement a progress bar in cp, would that patch be
accepted? Is there any reason not to do it?

Thank You,
Diogo Sousa


Here are two implementations.

First is mine and second is someone else's.

There is also implementation here http://sources.gentoo.org/gentoo/src/patchsets/coreutils/ according to [email protected]

Looks like a lot of people want this feature, invent their own bicycles and kludges waiting for it to be added to coreutils.

FreeBSD (hmm looks like MacOS too) have Ctrl+T SIGINFO to see how much time left to copy file and lot of tools support it.

Linux has nothing. I dont care that Posix doesn't describe SIGINFO, it's usefull, bsd has it and people like it.

I tried to implement SIGINFO in Linux kernel but failed to understand all tty stuff (which i could easily understand in freebsd kernel), and i'm not even trying to add new signal. So I could only replace Ctrl+\ SIGQUIT with SIGUSR1 which 'dd' does understand as print status and 'fsck' too. I'm very happy with this feature on my rescue USB flash.

So the best solution to sucking coreutils interactivity would be:
1) Implement some option (-B ?) to show dot progress bar AND show file name and percent completed on SIGUSR1.
2) Implement Ctrl+T to send SIGUSR1 or even implement SIGINFO if it's possible.
3) Add info print out to as many console tools as possible.
4) Ctrl+K to send SIGKILL would be nice (ok, ok at least we have MagicSysrq that does something like that, linux-only feature)

Yes I understand that nobody will do anything of that because (.... insert your argument here ....), well enjoy 0-1% OS market share)) Unfortunately i'm not kernel developer, well not even C programmer, and implementing this is far beyond my abilities. Actually even if I improve my skills and implement this you will not take my patches to mainstream, neither will kernel developers because (.... insert a lot of agruments here ....) so I will just improve my USB rescue system.



--- coreutils-8.5/src/copy.c	2010-04-20 23:52:04.000000000 +0400
+++ copy.c	2010-09-12 17:50:57.544775657 +0400
@@ -709,6 +709,20 @@
       buf_alloc = xmalloc (buf_size + buf_alignment_slop);
       buf = ptr_align (buf_alloc, buf_alignment);
 
+      int counter=0;
+      int writes_per_percent;
+      bool print_progress = false;
+ 
+      //decide should we print progress bar
+      if ((x->verbose) && (src_open_sb.st_size > 0))
+        print_progress = true;
+
+      //calculate number of writes per one percent of copy progress
+      if (print_progress)
+      {
+        writes_per_percent = (int)src_open_sb.st_size / (int)buf_size / 100;
+        printf("%d bytes: ",(int)src_open_sb.st_size);
+      }
       for (;;)
         {
           word *wp = NULL;
@@ -783,15 +797,29 @@
                   return_val = false;
                   goto close_src_and_dst_desc;
                 }
+
+              //print dot per percent of copy progress
+              if ((print_progress) && ((counter >= writes_per_percent) || (counter == 0)))
+                {
+                  counter=0;
+                  printf("*");
+                  fflush(stdout);
+                }
+              counter++;
+              
               last_write_made_hole = false;
 
               /* It is tempting to return early here upon a short read from a
                  regular file.  That would save the final read syscall for each
                  file.  Unfortunately that doesn't work for certain files in
                  /proc with linux kernels from at least 2.6.9 .. 2.6.29.  */
-            }
+            } 
         }
-
+        
+        //print newline after line of dots
+        if (print_progress)
+          printf(" Done!\n");
+        
       /* If the file ends with a `hole', we need to do something to record
          the length of the file.  On modern systems, calling ftruncate does
          the job.  On systems without native ftruncate support, we have to
diff -ru coreutils-8.8/src/copy.c coreutils-8.8-1/src/copy.c
--- coreutils-8.8/src/copy.c	2010-01-03 18:06:20.000000000 +0100
+++ coreutils-8.8-1/src/copy.c	2010-08-25 21:11:40.629752495 +0200
@@ -457,6 +457,56 @@
   return lchmod (name, mode);
 }
 
+/* BEGIN progress mod */
+static void file_progress_bar ( char * _cDest, int _iBarLength, int _iProgress, int _iTotal )
+{
+  // write number to progress bar
+  float fPercent = ( float ) _iProgress / ( float ) _iTotal * 100.f;
+  sprintf ( _cDest + ( _iBarLength - 6 ), "%4.1f", fPercent );
+  // remove zero
+  _cDest[_iBarLength - 2] = ' ';
+
+  // fill rest with '-'
+  int i;
+  for ( i = 1; i <= _iBarLength - 9; i++ )
+  {
+    if ( fPercent > ( float ) ( i - 1 ) / ( _iBarLength - 10 ) * 100.f )
+      _cDest[i] = '|';
+    else
+      _cDest[i] = '-';
+  }
+}
+
+int file_size_format ( char * _cDst, int _iSize, int _iCounter )
+{
+  int iCounter = _iCounter;
+  double dSize = ( double ) _iSize;
+  while ( dSize >= 1000. )
+  {
+    dSize /= 1024.;
+    iCounter++;
+  }
+
+  /* get unit */
+  char * sUnit;
+  if ( iCounter == 0 )
+    sUnit = "B";
+  else if ( iCounter == 1 )
+    sUnit = "KiB";
+  else if ( iCounter == 2 )
+    sUnit = "MiB";
+  else if ( iCounter == 3 )
+    sUnit = "GiB";
+  else if ( iCounter == 4 )
+    sUnit = "TiB";
+  else
+    sUnit = "N/A";
+
+  /* write number */
+  return sprintf ( _cDst, "%5.1f %s", dSize, sUnit );
+}
+/* END progress mod */
+
 /* Copy a regular file from SRC_NAME to DST_NAME.
    If the source file contains holes, copies holes and blocks of zeros
    in the source file as holes in the destination file.
@@ -704,8 +704,146 @@
       buf_alloc = xmalloc (buf_size + buf_alignment_slop);
       buf = ptr_align (buf_alloc, buf_alignment);
 
+      /* BEGIN progress mod */
+      /* create a field of 6 lines */
+      char ** cProgressField = ( char ** ) calloc ( 6, sizeof ( char * ) );
+      /* get console width */
+      int iBarLength = 80;
+      struct winsize win;
+      if ( ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &win) == 0 && win.ws_col > 0 )
+         iBarLength = win.ws_col;
+      /* create rows */
+      int it;
+      for ( it = 0; it < 6; it++ )
+      {
+        cProgressField[it] = ( char * ) malloc ( iBarLength + 1 );
+        /* init with spaces */
+        int j;
+        for ( j = 0; j < iBarLength; j++ )
+          cProgressField[it][j] = ' ';
+        cProgressField[it][iBarLength] = '\0';
+      }
+
+      /* global progress bar? */
+      if ( g_iTotalSize )
+      {
+        /* init global progress bar */
+        cProgressField[2][0] = '[';
+        cProgressField[2][iBarLength - 8] = ']';
+        cProgressField[2][iBarLength - 7] = ' ';
+        cProgressField[2][iBarLength - 1] = '%';
+
+        /* total size */
+        cProgressField[1][iBarLength - 11] = '/';
+        file_size_format ( cProgressField[1] + iBarLength - 9, g_iTotalSize, 1 );
+
+        /* show how many files were written */
+        int sum_length = sprintf ( cProgressField[1], "%d files copied so far...", g_iFilesCopied );
+        cProgressField[1][sum_length] = ' ';
+      }
+
+      /* truncate filename? */
+      int fn_length;
+      if ( strlen ( src_name ) > iBarLength - 22 )
+        fn_length =
+          sprintf ( cProgressField[4], "...%s", src_name + ( strlen ( src_name ) - iBarLength + 25 ) );
+      else
+        fn_length = sprintf ( cProgressField[4], "%s", src_name );
+      cProgressField[4][fn_length] = ' ';
+
+      /* filesize */
+      cProgressField[4][iBarLength - 11] = '/';
+      file_size_format ( cProgressField[4] + iBarLength - 9, src_open_sb.st_size, 0 );
+
+      int iCountDown = 1;
+      char * sProgressBar = cProgressField[5];
+      sProgressBar[0] = '[';
+      sProgressBar[iBarLength - 8] = ']';
+      sProgressBar[iBarLength - 7] = ' ';
+      sProgressBar[iBarLength - 1] = '%';
+
+      /* this will always save the time in between */
+      struct timeval last_time;
+      gettimeofday ( & last_time, NULL );
+      int last_size = g_iTotalWritten;
+      /* END progress mod */
+
       while (true)
         {
+          if (progress) {
+          /* BEGIN progress mod */
+          /* update countdown */
+          iCountDown--;
+          if ( iCountDown < 0 )
+            iCountDown = 100;
+
+          /* just print one line with the percentage, but not always */
+          if ( iCountDown == 0 )
+          {
+            /* calculate current speed */
+            struct timeval cur_time;
+            gettimeofday ( & cur_time, NULL );
+            int cur_size = g_iTotalWritten + n_read_total / 1024;
+            int usec_elapsed = cur_time.tv_usec - last_time.tv_usec;
+            double sec_elapsed = ( double ) usec_elapsed / 1000000.f;
+            sec_elapsed += ( double ) ( cur_time.tv_sec - last_time.tv_sec );
+            int copy_speed = ( int ) ( ( double ) ( cur_size - last_size )
+              / sec_elapsed );
+            char s_copy_speed[20];
+            file_size_format ( s_copy_speed, copy_speed, 1 );
+            /* update vars */
+            last_time = cur_time;
+            last_size = cur_size;
+
+            /* how many time has passed since the start? */
+            int isec_elapsed = cur_time.tv_sec - g_oStartTime.tv_sec;
+            int sec_remaining = ( int ) ( ( double ) isec_elapsed / cur_size
+              * g_iTotalSize ) - isec_elapsed;
+            int min_remaining = sec_remaining / 60;
+            sec_remaining -= min_remaining * 60;
+            int hours_remaining = min_remaining / 60;
+            min_remaining -= hours_remaining * 60;
+            /* print out */
+            sprintf ( cProgressField[3],
+              "Copying at %s/s (about %dh %dm %ds remaining)", s_copy_speed,
+              hours_remaining, min_remaining, sec_remaining );
+
+            int fs_len;
+            if ( g_iTotalSize )
+            {
+              /* global progress bar */
+              file_progress_bar ( cProgressField[2], iBarLength,
+                                  g_iTotalWritten + n_read_total / 1024, g_iTotalSize );
+
+              /* print the global status */
+              fs_len = file_size_format ( cProgressField[1] + iBarLength - 21,
+                                              g_iTotalWritten + n_read_total / 1024, 1 );
+              cProgressField[1][iBarLength - 21 + fs_len] = ' ';
+            }
+
+            /* current progress bar */
+            file_progress_bar ( sProgressBar, iBarLength, n_read_total, src_open_sb.st_size );
+
+            /* print the status */
+            fs_len = file_size_format ( cProgressField[4] + iBarLength - 21, n_read_total, 0 );
+            cProgressField[4][iBarLength - 21 + fs_len] = ' ';
+
+            /* print the field */
+            for ( it = g_iTotalSize ? 0 : 3; it < 6; it++ )
+            {
+              printf ( "\033[K%s\n", cProgressField[it] );
+              if ( strlen ( cProgressField[it] ) < iBarLength )
+                printf ( "" );
+            }
+            if ( g_iTotalSize )
+              printf ( "\r\033[6A" );
+            else
+              printf ( "\r\033[3A" );
+            fflush ( stdout );
+          }
+          /* END progress mod */
+          }
+
           word *wp = NULL;
 
           ssize_t n_read = read (source_desc, buf, buf_size);
@@ -788,6 +976,19 @@
                  /proc with linux kernels from at least 2.6.9 .. 2.6.29.  */
             }
         }
+if (progress) {
+      /* BEGIN progress mod */
+      /* update total size */
+      g_iTotalWritten += n_read_total / 1024;
+      g_iFilesCopied++;
+
+      int i;
+      for ( i = 0; i < 6; i++ )
+        free ( cProgressField[i] );
+      free ( cProgressField );
+      /* END progress mod */
+}
+
 
       /* If the file ends with a `hole', we need to do something to record
          the length of the file.  On modern systems, calling ftruncate does
diff -ru coreutils-8.8/src/copy.h coreutils-8.8-1/src/copy.h
--- coreutils-8.8/src/copy.h	2010-01-03 18:06:20.000000000 +0100
+++ coreutils-8.8-1/src/copy.h	2010-08-25 21:11:40.629752495 +0200
@@ -222,6 +222,9 @@
      Create destination directories as usual. */
   bool symbolic_link;
 
+  /* if true, draw a nice progress bar on screen */
+  bool progress_bar;
+
   /* If true, do not copy a nondirectory that has an existing destination
      with the same or newer modification time. */
   bool update;
@@ -280,4 +283,15 @@
 bool chown_failure_ok (struct cp_options const *);
 mode_t cached_umask (void);
 
+/* BEGIN progress mod */
+int file_size_format ( char * _cDst, int _iSize, int _iCounter );
+
+long g_iTotalSize;
+long g_iTotalWritten;
+int g_iFilesCopied;
+struct timeval g_oStartTime;
+int g_iTotalFiles;
+bool progress;
+/* END progress mod */
+
 #endif
diff -ru coreutils-8.8/src/cp.c coreutils-8.8-1/src/cp.c
--- coreutils-8.8/src/cp.c	2010-01-04 16:46:06.000000000 +0100
+++ coreutils-8.8-1/src/cp.c	2010-08-25 21:11:40.629752495 +0200
@@ -139,6 +139,7 @@
   {"target-directory", required_argument, NULL, 't'},
   {"update", no_argument, NULL, 'u'},
   {"verbose", no_argument, NULL, 'v'},
+  {"progress-bar", no_argument, NULL, 'g'},
   {GETOPT_HELP_OPTION_DECL},
   {GETOPT_VERSION_OPTION_DECL},
   {NULL, 0, NULL, 0}
@@ -176,6 +177,7 @@
   -f, --force                  if an existing destination file cannot be\n\
                                  opened, remove it and try again (redundant if\n\
                                  the -n option is used)\n\
+  -g, --progress-bar           add progress-bar\n\
   -i, --interactive            prompt before overwrite (overrides a previous -n\n\
                                   option)\n\
   -H                           follow command-line symbolic links in SOURCE\n\
@@ -612,6 +614,57 @@
                quote (file[n_files - 1]));
     }
 
+    struct timeval start_time;
+if (progress) {
+    /* BEGIN progress mod */
+    g_iTotalSize = 0;
+    g_iFilesCopied = 0;
+    g_iTotalWritten = 0;
+
+    /* save time */
+    gettimeofday ( & start_time, NULL );
+    g_oStartTime = start_time;
+
+    printf ( "Calculating total size... \r" );
+    fflush ( stdout );
+    long iTotalSize = 0;
+    int iFiles = n_files;
+    if ( ! target_directory )
+      iFiles = n_files - 1;
+    int j;
+    for (j = 0; j < iFiles; j++)
+    {
+      /* call du -s for each file */
+      /* create command */
+      char command[1024];
+      sprintf ( command, "du -s \"%s\"", file[j] );
+      /* TODO: replace all quote signs in file[i] */
+
+      FILE *fp;
+      char output[1024];
+
+      /* run command */
+      fp = popen(command, "r");
+      if (fp == NULL || fgets(output, sizeof(output)-1, fp) == NULL) {
+        printf("failed to run du.\n" );
+      }
+      else
+      {
+        /* isolate size */
+        strchr ( output, '\t' )[0] = '\0';
+        iTotalSize += atol ( output );
+
+        printf ( "Calculating total size... %ld\r", iTotalSize );
+        fflush ( stdout );
+      }
+
+      /* close */
+      pclose(fp);
+    }
+    g_iTotalSize = iTotalSize;
+    /* END progress mod */
+}
+
   if (target_directory)
     {
       /* cp file1...filen edir
@@ -754,6 +807,46 @@
       ok = copy (source, new_dest, 0, x, &unused, NULL);
     }
 
+if (progress) {
+    /* BEGIN progress mod */
+    /* remove everything */
+    int i;
+    if ( g_iTotalSize )
+    {
+      for ( i = 0; i < 6; i++ )
+        printf ( "\033[K\n" );
+      printf ( "\r\033[6A" );
+    }
+    else
+    {
+      for ( i = 0; i < 3; i++ )
+        printf ( "\033[K\n" );
+      printf ( "\r\033[3A" );
+    }
+
+    /* save time */
+    struct timeval end_time;
+    gettimeofday ( & end_time, NULL );
+    int usec_elapsed = end_time.tv_usec - start_time.tv_usec;
+    double sec_elapsed = ( double ) usec_elapsed / 1000000.f;
+    sec_elapsed += ( double ) ( end_time.tv_sec - start_time.tv_sec );
+
+    /* get total size */
+    char sTotalWritten[20];
+    file_size_format ( sTotalWritten, g_iTotalSize, 1 );
+    /* TODO: using g_iTotalWritten would be more correct, but is less accurate */
+
+    /* calculate speed */
+    int copy_speed = ( int ) ( ( double ) g_iTotalWritten / sec_elapsed );
+    char s_copy_speed[20];
+    file_size_format ( s_copy_speed, copy_speed, 1 );
+
+    /* good-bye message */
+    printf ( "%d files (%s) copied in %.1f seconds (%s/s).\n", g_iFilesCopied, sTotalWritten,
+             sec_elapsed, s_copy_speed );
+    /* END progress mod */
+}
+
   return ok;
 }
 
@@ -785,6 +878,7 @@
   x->recursive = false;
   x->sparse_mode = SPARSE_AUTO;
   x->symbolic_link = false;
+  x->progress_bar = false;
   x->set_mode = false;
   x->mode = 0;
 
@@ -923,7 +1017,7 @@
      we'll actually use backup_suffix_string.  */
   backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
 
-  while ((c = getopt_long (argc, argv, "abdfHilLnprst:uvxPRS:T",
+  while ((c = getopt_long (argc, argv, "abdfgHilLnprst:uvxPRS:T",
                            long_opts, NULL))
          != -1)
     {
@@ -975,6 +1069,10 @@
           x.unlink_dest_after_failed_open = true;
           break;
 
+        case 'g':
+          progress = true;
+          break;
+
         case 'H':
           x.dereference = DEREF_COMMAND_LINE_ARGUMENTS;
           break;
diff -ru coreutils-8.8/src/mv.c coreutils-8.8-1/src/mv.c
--- coreutils-8.8/src/mv.c	2010-01-03 18:06:20.000000000 +0100
+++ coreutils-8.8-1/src/mv.c	2010-08-25 21:11:40.629752495 +0200
@@ -64,6 +64,7 @@
   {"target-directory", required_argument, NULL, 't'},
   {"update", no_argument, NULL, 'u'},
   {"verbose", no_argument, NULL, 'v'},
+  {"progress-bar", no_argument, NULL, 'g'},
   {GETOPT_HELP_OPTION_DECL},
   {GETOPT_VERSION_OPTION_DECL},
   {NULL, 0, NULL, 0}
@@ -159,13 +160,100 @@
 static bool
 do_move (const char *source, const char *dest, const struct cp_options *x)
 {
+
+  struct timeval start_time;
+
+  if(progress) {
+    /* BEGIN progress mod */
+    g_iTotalSize = 0;
+    g_iFilesCopied = 0;
+    g_iTotalWritten = 0;
+
+    gettimeofday (& start_time, NULL);
+    g_oStartTime = start_time;
+
+    printf ("Calculating total size... \r");
+    fflush (stdout);
+    long iTotalSize = 0;
+    /* call du -s for each file */
+    /* create command */
+    char command[1024];
+    sprintf ( command, "du -s \"%s\"", source );
+    /* TODO: replace all quote signs in file[i] */
+
+    FILE *fp;
+    char output[1024];
+
+    /* run command */
+    fp = popen(command, "r");
+    if (fp == NULL || fgets(output, sizeof(output)-1, fp) == NULL) {
+      printf("failed to run du.\n" );
+    }
+    else
+    {
+      /* isolate size */
+      strchr ( output, '\t' )[0] = '\0';
+      iTotalSize += atol ( output );
+      printf ( "Calculating total size... %ld\r", iTotalSize );
+      fflush ( stdout );
+    }
+
+    /* close */
+    pclose(fp);
+    g_iTotalSize = iTotalSize;
+    /* END progress mod */
+
+  }
+
   bool copy_into_self;
   bool rename_succeeded;
   bool ok = copy (source, dest, false, x, &copy_into_self, &rename_succeeded);
 
+  if (progress) {
+    /* BEGIN progress mod */
+    /* remove everything */
+    int i;
+    if ( g_iTotalSize )
+    {
+      for ( i = 0; i < 6; i++ )
+        printf ( "\033[K\n" );
+      printf ( "\r\033[6A" );
+    }
+    else
+    {
+      for ( i = 0; i < 3; i++ )
+        printf ( "\033[K\n" );
+      printf ( "\r\033[3A" );
+    }
+
+    /* save time */
+    struct timeval end_time;
+    gettimeofday ( & end_time, NULL );
+    int usec_elapsed = end_time.tv_usec - start_time.tv_usec;
+    double sec_elapsed = ( double ) usec_elapsed / 1000000.f;
+    sec_elapsed += ( double ) ( end_time.tv_sec - start_time.tv_sec );
+
+    /* get total size */
+    char sTotalWritten[20];
+    file_size_format ( sTotalWritten, g_iTotalSize, 1 );
+    /* TODO: using g_iTotalWritten would be more correct, but is less accurate */
+
+    /* calculate speed */
+    int copy_speed = ( int ) ( ( double ) g_iTotalWritten / sec_elapsed );
+    char s_copy_speed[20];
+    file_size_format ( s_copy_speed, copy_speed, 1 );
+
+    /* good-bye message */
+    printf ( "%d files (%s) moved in %.1f seconds (%s/s).\n", g_iFilesCopied, sTotalWritten,
+             sec_elapsed, s_copy_speed );
+    /* END progress mod */
+  }
+
   if (ok)
     {
       char const *dir_to_remove;
+
+
       if (copy_into_self)
         {
           /* In general, when copy returns with copy_into_self set, SOURCE is
@@ -298,6 +386,7 @@
       --backup[=CONTROL]       make a backup of each existing destination file\n\
   -b                           like --backup but does not accept an argument\n\
   -f, --force                  do not prompt before overwriting\n\
+  -g, --progress-bar	       add progress-bar\n\
   -i, --interactive            prompt before overwrite\n\
   -n, --no-clobber             do not overwrite an existing file\n\
 If you specify more than one of -i, -f, -n, only the final one takes effect.\n\
@@ -366,7 +455,7 @@
      we'll actually use backup_suffix_string.  */
   backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
 
-  while ((c = getopt_long (argc, argv, "bfint:uvS:T", long_options, NULL))
+  while ((c = getopt_long (argc, argv, "bfint:uvgS:T", long_options, NULL))
          != -1)
     {
       switch (c)
@@ -411,6 +500,9 @@
         case 'v':
           x.verbose = true;
           break;
+	case 'g':
+	  progress = true;
+	  break;
         case 'S':
           make_backups = true;
           backup_suffix_string = optarg;

Reply via email to