From 55405140f776929388d2701877eff877da1aa93b Mon Sep 17 00:00:00 2001
From: yorhel <yorhel@ce56bc8d-f834-0410-b703-f827bd498a76>
Date: Sat, 11 Aug 2007 20:01:16 +0000
Subject: [PATCH] * (BETA) Implemented file list exporting and importing *
 Added file format specification (README.fileformat) * Moved settingsCli in
 settings.c to parseCli in main.c * Bugfix: drawError in calc.c was not shown
 when an error occurred while reading the main dir

git-svn-id: svn://blicky.net/ncdu/trunk@25 ce56bc8d-f834-0410-b703-f827bd498a76
---
 README.fileformat |  80 ++++++++++++++
 src/Makefile.am   |   2 +-
 src/calc.c        |  12 ++-
 src/export.c      | 265 ++++++++++++++++++++++++++++++++++++++++++++++
 src/main.c        | 104 ++++++++++++++++--
 src/ncdu.h        |  27 ++++-
 src/settings.c    |  58 +---------
 7 files changed, 481 insertions(+), 67 deletions(-)
 create mode 100644 README.fileformat
 create mode 100644 src/export.c

diff --git a/README.fileformat b/README.fileformat
new file mode 100644
index 0000000..30e2554
--- /dev/null
+++ b/README.fileformat
@@ -0,0 +1,80 @@
+                         ncdu file list format v1                    11-08-2007
+
+
+This document describes the file format of the file list export feature of
+ncdu. This document was used as a reference for implementing the export
+feature, and may be useful to anyone who wants to read or write these files.
+
+
+
+definitions
+  byte  = unsigned 8bit integer
+  2uint = unsigned 16bit integer
+  4uint = unsigned 32bit integer
+  8int  = signed 64bit integer
+  8uint = unsigned 64bit integer
+
+  All integers are in network (big-endian) byte order.
+
+
+
+header
+ ssssvnttttttttiiii
+
+  s = "ncdu" (hex: 6e 63 64 75)
+  v = version of the file format, always 1 (hex: 01)
+  n = NULL-terminated string containing the package name and version of the
+      program that created this file. ncdu 1.3 would be "ncdu 1.3\0". Maximum
+      of 64 bytes including the terminating NULL byte.
+  t = 8int, unix timestamp of when the file was created
+  i = 4uint, total number of directory items
+
+
+
+directory item
+ llfssssssssaaaaaaaaddddn
+
+  l = 2uint, level. 0 for parent, 1 for sub, 2 for sub sub, etc. (see below)
+  f = byte, flags: (mostly the same as defined in ncdu.h)
+       01  item is a directory
+       02  item is a file
+       04  error while reading this item
+       08  item was excluded because it was on an other filesystem
+       10  item was exluded using exclude patterns
+  s = 8uint, disk usage of current item (st_blocks * 512)
+  a = 8uint, apparent size of current item (st_size)
+  n = NULL-terminated string, name of the current item, absolute maximum length
+      of 8192 bytes including the terminating NULL-byte.
+
+
+
+global layout
+  An ncdu datafile always starts with the header, this header also specifies
+  the number of directory items. All data after the last item should be
+  ignored.
+
+  After the header follows the list of directory items. The first directory
+  item should be the parent directory. This is the only item to have a full and
+  absolute path as name, all other items should only include the name of the
+  item itself. This is also the only item to have a level of 0.
+
+  The second directory item should level set to 1, and should be an item
+  located in the parent directory. Any higher level than the previous item
+  indicates that the item is located in the previous item, the same level
+  indicates this item is located in the same directory as the previous item,
+  and a lower level indicates that this item is located in de same directory
+  as the parent directory of the previous item.
+
+  The disk usage and apparent size of each directory item is accumulated, it
+  should already include the sizes of the subitems.
+
+  Example showing the use of levels and names:
+
+    [header](items=7)
+    [directory item](level=0, name=/parent/dir)       /parent/dir
+    [directory item](level=1, name=file1)             /parent/dir/file1
+    [directory item](level=1, name=dir1)              /parent/dir/dir1
+    [directory item](level=2, name=sfile1)            /parent/dir/dir1/sfile1
+    [directory item](level=2, name=sfile2)            /parent/dir/dir1/sfile2
+    [directory item](level=1, name=file2)             /parent/dir/file2
+    [directory item](level=1, name=file3)             /parent/dir/file3
diff --git a/src/Makefile.am b/src/Makefile.am
index 5f180c7..2fa7a94 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,5 +1,5 @@
 bin_PROGRAMS = ncdu
 
-ncdu_SOURCES = browser.c calc.c main.c settings.c util.c exclude.c help.c delete.c
+ncdu_SOURCES = browser.c calc.c main.c settings.c util.c exclude.c help.c delete.c export.c
 
 noinst_HEADERS = ncdu.h
diff --git a/src/calc.c b/src/calc.c
index 4701b48..ed32423 100644
--- a/src/calc.c
+++ b/src/calc.c
@@ -192,6 +192,7 @@ static void drawError(char *dir) {
   mvwprintw(err, 5, 9, "could not open %s", cropdir(dir, 34));
   mvwaddstr(err, 6, 3, "press any key to continue...");
 
+  refresh();
   wrefresh(err);
   delwin(err);
 }
@@ -202,6 +203,10 @@ int updateProgress(char *path) {
   struct timeval tv;
   int ch;
 
+ /* don't do anything if s_export is set (ncurses isn't initialized) */
+  if(s_export)
+    return(1);
+
  /* check for input or screen resizes */
   nodelay(stdscr, 1);
   while((ch = getch()) != ERR) {
@@ -374,12 +379,17 @@ struct dir *showCalc(char *path) {
   lastupdate = 999;
 
  /* init parent dir */
-  if(rpath(path, tmp) == NULL || lstat(tmp, &fs) != 0) {
+  if(rpath(path, tmp) == NULL || lstat(tmp, &fs) != 0 || !S_ISDIR(fs.st_mode)) {
+    if(s_export) {
+      printf("Error: could not open %s\n", path);
+      exit(1);
+    }
     do {
       ncresize();
       if(dat != NULL)
         drawBrowser(0);
       drawError(path);
+      refresh();
     } while (getch() == KEY_RESIZE);
     return(NULL);
   }
diff --git a/src/export.c b/src/export.c
new file mode 100644
index 0000000..5d6545c
--- /dev/null
+++ b/src/export.c
@@ -0,0 +1,265 @@
+/* ncdu - NCurses Disk Usage 
+  
+  Copyright (c) 2007 Yoran Heling
+
+  Permission is hereby granted, free of charge, to any person obtaining
+  a copy of this software and associated documentation files (the
+  "Software"), to deal in the Software without restriction, including
+  without limitation the rights to use, copy, modify, merge, publish,
+  distribute, sublicense, and/or sell copies of the Software, and to
+  permit persons to whom the Software is furnished to do so, subject to
+  the following conditions:
+  
+  The above copyright notice and this permission notice shall be included
+  in all copies or substantial portions of the Software.
+  
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include "ncdu.h"
+
+
+/* !WARNING! There is currently *NO* error handling at all */
+
+
+/*
+ *   E X P O R T I N G
+*/
+
+
+unsigned int ilevel;
+
+
+#define writeInt(hl, word, bytes) _writeInt(hl, (unsigned char *) &word, bytes, sizeof(word))
+
+/* Write any integer in network byte order.
+ * This function always writes the number of bytes specified by storage to the
+ * file, disregarding the actual size of word. If the actual size is smaller
+ * than the storage, the number will be preceded by null-bytes. If the actual
+ * size is greater than the storage, we assume the value we want to store fits
+ * in the storage size, disregarding any overflow.
+*/
+void _writeInt(FILE *wr, unsigned char *word, int storage, int size) {
+  unsigned char buf[8]; /* should be able to store any integer type */
+  int i;
+
+ /* clear the buffer */
+  memset(buf, 0, 8);
+
+ /* store the integer in the end of the buffer, in network byte order */
+  if(IS_BIG_ENDIAN)
+    memcpy(buf+(8-size), word, size);
+  else
+    for(i=0; i<size; i++)
+      buf[8-size+i] = word[size-1-i];
+
+ /* write only the last bytes of the buffer (as specified by storage) to the file */
+  fwrite(buf+(8-storage), 1, storage, wr);
+}
+
+
+/* recursive */
+long calcItems(struct dir *dr) {
+  int count = 0;
+  do {
+    if(dr->flags & FF_PAR)
+      continue;
+    if(dr->sub)
+      count += calcItems(dr->sub);
+    count++;
+  } while((dr = dr->next) != NULL);
+  return(count);
+}
+
+
+/* also recursive */
+void writeDirs(FILE *wr, struct dir *dr) {
+  unsigned char f;
+  
+  do {
+    if(dr->flags & FF_PAR)
+      continue;
+  
+   /* flags - the slow but portable way */
+    f = 0;
+    if(dr->flags & FF_DIR)   f += EF_DIR;
+    if(dr->flags & FF_FILE)  f += EF_FILE;
+    if(dr->flags & FF_ERR)   f += EF_ERR;
+    if(dr->flags & FF_OTHFS) f += EF_OTHFS;
+    if(dr->flags & FF_EXL)   f += EF_EXL;
+
+    writeInt(wr, ilevel, 2);
+    fwrite(&f, 1, 1, wr);
+    writeInt(wr, dr->size, 8);
+    writeInt(wr, dr->asize, 8);
+    fprintf(wr, "%s%c", dr->name, 0);
+
+    if(dr->sub != NULL) {
+      ilevel++;
+      writeDirs(wr, dr->sub);
+      ilevel--;
+    }
+
+  } while((dr = dr->next) != NULL);
+}
+
+
+void exportFile(char *dest, struct dir *src) {
+  FILE *wr;
+  struct timeval tv;
+  long items;
+
+  wr = fopen(dest, "w");
+
+  /* header */
+  fprintf(wr, "ncdu%c%s%c", 1, PACKAGE_STRING, 0);
+
+  gettimeofday(&tv, NULL);
+  writeInt(wr, tv.tv_sec, 8);
+
+  items = calcItems(src);
+  writeInt(wr, items, 4);
+
+  /* the directory items */
+  ilevel = 0;
+  writeDirs(wr, src);
+
+  fclose(wr);
+}
+
+
+
+
+/*
+ *  I M P O R T I N G
+*/
+
+
+#define readInt(hl, word, bytes) _readInt(hl, (unsigned char *) &word, bytes, sizeof(word))
+
+/* reverse of writeInt */
+void _readInt(FILE *rd, unsigned char *word, int storage, int size) {
+  unsigned char buf[8];
+  int i;
+
+ /* clear the buffer */
+  memset(buf, 0, 8);
+
+ /* read integer to the end of the buffer */
+  fread(buf+(8-storage), 1, storage, rd);
+
+ /* copy buf to word, in host byte order */
+  if(IS_BIG_ENDIAN)
+    memcpy(word, buf+(8-size), size);
+  else
+    for(i=0; i<size; i++)
+      word[i] = buf[7-i];
+}
+
+
+struct dir *importFile(char *filename) {
+  unsigned char buf[8];
+  FILE *rd;
+  int i, item;
+  unsigned long items;
+  unsigned int level;
+  struct dir *prev, *cur, *tmp, *parent;
+
+  rd = fopen(filename, "r");
+
+ /* check filetype */
+  fread(buf, 1, 5, rd);
+  if(buf[0] != 'n' || buf[1] != 'c' || buf[2] != 'd'
+      || buf[3] != 'u' || buf[4] != (char) 1)
+    return(NULL);
+
+ /* package name, version and timestamp are ignored for now */
+  for(i=0; i<=64 && fgetc(rd) != 0; i++) ;
+  fread(buf, 1, 8, rd);
+
+ /* number of items is not ignored */
+  readInt(rd, items, 4);
+
+ /* and now start reading the directory items */
+  level = 0;
+  prev = NULL;
+  for(item=0; item<items; item++) {
+    unsigned int curlev;
+    unsigned char flags, name[8192];
+    int ch;
+    
+    readInt(rd, curlev, 2);
+    flags = fgetc(rd);
+
+    cur = calloc(sizeof(struct dir), 1);
+    if(!prev)
+      parent = cur;
+    else if(curlev > level) {
+     /* create a reference to the parent dir... meh, stupid hack */
+      if(curlev > 1) {
+        tmp = calloc(sizeof(struct dir), 1);
+        tmp->name = malloc(3);
+        strcpy(tmp->name, "..");
+        tmp->flags |= FF_PAR;
+        prev->sub = tmp;
+        tmp->next = cur;
+        tmp->parent = prev;
+      } else
+        prev->sub = cur;
+      cur->parent = prev;
+    } else if(curlev == level) {
+      prev->next = cur;
+      cur->parent = prev->parent;
+    } else {
+      for(i=level; i>curlev; i--)
+        prev = prev->parent;
+      prev->next = cur;
+      cur->parent = prev->parent;
+    }
+
+   /* flags - again, the slow but portable way */
+    if(flags & EF_DIR)   cur->flags |= FF_DIR;
+    if(flags & EF_FILE)  cur->flags |= FF_FILE;
+    if(flags & EF_OTHFS) cur->flags |= FF_OTHFS;
+    if(flags & EF_EXL)   cur->flags |= FF_EXL;
+    if(flags & EF_ERR) {
+      cur->flags |= FF_ERR;
+      for(tmp = cur->parent; tmp != NULL; tmp = tmp->parent)
+        tmp->flags |= FF_SERR;
+    }
+
+   /* sizes */
+    readInt(rd, cur->size, 8);
+    readInt(rd, cur->asize, 8);
+
+   /* name */
+    for(i=0; i<8192; i++) {
+      ch = fgetc(rd);
+      name[i] = (unsigned char) ch;
+      if(ch == 0)
+        break;
+    }
+    cur->name = malloc(i+1);
+    strcpy(cur->name, name);
+
+   /* update 'items' of parent dirs */
+    if(!(cur->flags & FF_EXL))
+      for(tmp = cur->parent; tmp != NULL; tmp = tmp->parent)
+        tmp->items++;
+
+    level = curlev;
+    prev = cur;
+  }
+  
+  return(parent);
+}
+
+
+
diff --git a/src/main.c b/src/main.c
index 11e0b8e..2d50ab4 100644
--- a/src/main.c
+++ b/src/main.c
@@ -28,14 +28,107 @@
 /* check ncdu.h what these are for */
 struct dir *dat;
 int winrows, wincols;
-char sdir[PATH_MAX];
+char sdir[PATH_MAX], *s_export;
 int sflags, bflags, sdelay, bgraph;
 
+
+/* parse command line */
+void parseCli(int argc, char **argv) {
+  int i, j;
+
+ /* load defaults */
+  memset(sdir, 0, PATH_MAX);
+  getcwd(sdir, PATH_MAX);
+  sflags = 0;
+  sdelay = 100;
+  bflags = BF_SIZE | BF_DESC;
+  s_export = NULL;
+  sdir[0] = '\0';
+
+ /* read from commandline */
+  for(i=1; i<argc; i++) {
+    if(argv[i][0] == '-') {
+     /* flags requiring arguments */
+      if(argv[i][1] == 'X' || !strcmp(argv[i], "--exclude-from") || !strcmp(argv[i], "--exclude")
+          || argv[i][1] == 'e' || argv[i][1] == 'l') {
+        if(i+1 >= argc) {
+          printf("Option %s requires an argument\n", argv[i]);
+          exit(1);
+        }
+        if(argv[i][1] == 'e')
+          s_export = argv[++i];
+        else if(strcmp(argv[i], "--exclude") == 0)
+          addExclude(argv[++i]);
+        else if(addExcludeFile(argv[++i])) {
+          printf("Can't open %s: %s\n", argv[i], strerror(errno));
+          exit(1);
+        }
+        continue;
+      }
+     /* small flags */
+      for(j=1; j < strlen(argv[i]); j++)
+        switch(argv[i][j]) {
+          case 'x': sflags |= SF_SMFS; break;
+          case 'q': sdelay = 2000;     break;
+          case '?':
+          case 'h':
+            printf("ncdu [-ahvx] [dir]\n\n");
+            printf("  -h  This help message\n");
+            printf("  -q  Low screen refresh interval when calculating\n");
+            printf("  -v  Print version\n");
+            printf("  -x  Same filesystem\n");
+            exit(0);
+          case 'v':
+            printf("ncdu %s\n", PACKAGE_VERSION);
+            exit(0);  
+          default:
+            printf("Unknown option: -%c\n", argv[i][j]);
+            exit(1);
+        }
+    } else {
+      strcpy(sdir, argv[i]);
+    }
+  }
+  if(s_export && !sdir[0]) {
+    printf("Can't export file list: no directory specified!\n");
+    exit(1);
+  }
+}
+
+
+/* if path is a file: import file list
+ * if path is a dir:  calculate directory */
+struct dir *loadDir(char *path) {
+  struct stat st;
+  
+  if(stat(path, &st) < 0) {
+    if(s_export) {
+      printf("Error: Can't open %s!", path);
+      exit(1);
+    }
+    return(showCalc(path));
+  }
+
+  if(S_ISREG(st.st_mode))
+    return(importFile(path));
+  else
+    return(showCalc(path));
+}
+
+
 /* main program */
 int main(int argc, char **argv) {
-  int gd;
+  dat = NULL;
+
+  parseCli(argc, argv);
+
+ /* only export file, don't init ncurses */
+  if(s_export) {
+    dat = loadDir(sdir);
+    exportFile(s_export, dat);
+    exit(0);
+  }
 
-  gd = settingsCli(argc, argv);
   initscr();
   cbreak();
   noecho();
@@ -43,11 +136,10 @@ int main(int argc, char **argv) {
   keypad(stdscr, TRUE);
   ncresize();
   
-  if(gd && settingsWin())
+  if(!sdir[0] && settingsWin())
     goto mainend;
 
-  dat = NULL;
-  while((dat = showCalc(sdir)) == NULL)
+  while((dat = loadDir(sdir)) == NULL)
     if(settingsWin())
       goto mainend;
 
diff --git a/src/ncdu.h b/src/ncdu.h
index 9ac7738..ef122ee 100644
--- a/src/ncdu.h
+++ b/src/ncdu.h
@@ -75,6 +75,14 @@
 # define S_ISLNK(x) (x & S_IFLNK)
 #endif
 
+/* There are many ways to test the endianness on a system, however, I couldn't
+ * find a universal way to do this, so I used this small hack that should work
+ * on any system. */
+static unsigned int endian_test = 1;
+#define IS_BIG_ENDIAN (!(*(char *) &endian_test))
+
+
+
 /*
  *    G L O B A L   F L A G S
  */
@@ -104,15 +112,23 @@
 #define BF_SORT   0x20 /* no need to resort, list is already in correct order */
 #define BF_AS     0x40 /* show apparent sizes instead of disk usage */
 
+/* Export Flags */
+#define EF_DIR    0x01
+#define EF_FILE   0x02
+#define EF_ERR    0x04
+#define EF_OTHFS  0x08
+#define EF_EXL    0x10
+
+
 
 /*
  *    S T R U C T U R E S
  */
 struct dir {
   struct dir *parent, *next, *sub;
-  char *name;
+  unsigned char *name;
   off_t size, asize;
-  unsigned int items;
+  unsigned long items;
   unsigned char flags;
 }; 
 
@@ -127,7 +143,7 @@ extern struct dir *dat;
 /* updated when window is resized */
 extern int winrows, wincols;
 /* global settings */
-extern char sdir[PATH_MAX];
+extern char sdir[PATH_MAX], *s_export;
 extern int sflags, bflags, sdelay, bgraph;
 
 
@@ -142,7 +158,6 @@ extern void ncresize(void);
 extern struct dir * freedir(struct dir *);
 extern char *getpath(struct dir *, char *);
 /* settings.c */
-extern int settingsCli(int, char **);
 extern int settingsWin(void);
 /* calc.c */
 extern struct dir *showCalc(char *);
@@ -157,4 +172,6 @@ extern struct dir *showDelete(struct dir *);
 extern void addExclude(char *);
 extern int addExcludeFile(char *);
 extern int matchExclude(char *);
-
+/* export.c */
+extern void exportFile(char *, struct dir *);
+extern struct dir *importFile(char *);
diff --git a/src/settings.c b/src/settings.c
index 45fc9df..0db708d 100644
--- a/src/settings.c
+++ b/src/settings.c
@@ -26,60 +26,6 @@
 #include "ncdu.h"
 
 
-int settingsCli(int argc, char **argv) {
-  int i, j;
-  char gotdir = 0;
-
- /* load defaults */
-  memset(sdir, 0, PATH_MAX);
-  getcwd(sdir, PATH_MAX);
-  sflags = 0;
-  sdelay = 100;
-  bflags = BF_SIZE | BF_DESC;
-
- /* read from commandline */
-  for(i=1; i<argc; i++) {
-    if(argv[i][0] == '-') {
-      if(argv[i][1] == 'X' || strcmp(argv[i], "--exclude-from") == 0 || strcmp(argv[i], "--exclude") == 0) {
-        if(i+1 >= argc) {
-          printf("Option %s requires an argument\n", argv[i]);
-          exit(1);
-        }
-        if(strcmp(argv[i], "--exclude") == 0)
-          addExclude(argv[++i]);
-        else if(addExcludeFile(argv[++i])) {
-          printf("Can't open %s: %s\n", argv[i], strerror(errno));
-          exit(1);
-        }
-        continue;
-      }
-      for(j=1; j < strlen(argv[i]); j++)
-        switch(argv[i][j]) {
-          case 'x': sflags |= SF_SMFS; break;
-          case 'q': sdelay = 2000;     break;
-          case '?':
-          case 'h':
-            printf("ncdu [-ahvx] [dir]\n\n");
-            printf("  -h    This help message\n");
-            printf("  -q x  Set the refresh interval in seconds\n");
-            printf("  -v    Print version\n");
-            printf("  -x    Same filesystem\n");
-            exit(0);
-          case 'v':
-            printf("ncdu %s\n", PACKAGE_VERSION);
-            exit(0);  
-          default:
-            printf("Unknown option: -%c\n", argv[i][j]);
-            exit(1);
-        }
-    } else {
-      strcpy(sdir, argv[i]);
-      gotdir = 1;
-    }
-  }
-  return(gotdir ? 0 : 1);
-}
-
 int settingsGet(void) {
   WINDOW *set;
   FORM *setf;
@@ -87,6 +33,10 @@ int settingsGet(void) {
   int w, h, cx, cy, i, j, ch;
   int fw, fh, fy, fx, fnrow, fnbuf;
   char tmp[10], *buf = "", rst = 0;
+
+  if(!sdir[0])
+    getcwd(sdir, PATH_MAX);
+
   erase();
   refresh();
                     /*  h,  w, y,  x  */
-- 
GitLab