summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Campbell <ijc@hellion.org.uk>2014-11-16 11:24:11 +0000
committerLars Wirzenius <liw@liw.fi>2014-12-06 19:02:39 +0200
commitda263719bc13e65b8f5948e657a4b2555523d509 (patch)
tree39f2ac63d12cb15ca9a7b18110a31f98d15649c5
parentce0e45fece3dcec7faace976b30d99314f2d4c15 (diff)
downloadobnam-da263719bc13e65b8f5948e657a4b2555523d509.tar.gz
Implement support for listing a generation in kdirstat.cache format
This implements an ls variant whose output format is compatible with the kdirstat cache format. The best reference I can find for this is from an old fork: https://github.com/thomas-joiner/k4dirstat/blob/master/kdirstat/cache-file-format.txt Recent versions of k4dirstat appear to be able to read the files produced just fine, although this document is not present in their source AFAICT. I find k4dirstat to be an excellent way to visualise what is included in a backup, e.g. for the purposes of excluding file types or marking directories with a CACHEDIR.TAG. My previous solution involved post-processing the output of ls which require a ssh to the machine with the files (for stat() purposes). This approach pulls all of the data out of the obnam repo, so is much faster, less hacky and gets the status of the actual generation and not the current files etc.
-rw-r--r--manual/en/120-misc.mdwn16
-rw-r--r--obnam.1.in7
-rw-r--r--obnamlib/plugins/show_plugin.py69
3 files changed, 80 insertions, 12 deletions
diff --git a/manual/en/120-misc.mdwn b/manual/en/120-misc.mdwn
index 0ed6c7fd..d80e8231 100644
--- a/manual/en/120-misc.mdwn
+++ b/manual/en/120-misc.mdwn
@@ -3,3 +3,19 @@ Other stuff
This chapter discusses topics that do not warrant a chapter of their
own, such as compressing backups and running Obnam from cron.
+
+k4dirstat cache files
+---------------------
+
+[k4dirstat] is a utility for visualising the disk space used by a
+directory tree. Obnam's `kdirstat` command can be used to produce a
+listing of the contents of a generation in a format which can be read
+by k4dirstat using `File`, `Read Cache File` from the k4dirstat
+menu. e.g.
+
+ $ obnam kdirstat --client CLIENT --generation GENID > CLIENT.kdirstat.cache
+ $ gzip -v9 CLIENT.kdirstat.cache # OPTIONAL
+
+`CLIENT.kdirstat.cache[.gz]` can now be read by `k4dirstat`.
+
+[k4dirstat]: https://bitbucket.org/jeromerobert/k4dirstat/wiki/Home
diff --git a/obnam.1.in b/obnam.1.in
index 50b0092a..c0c7185e 100644
--- a/obnam.1.in
+++ b/obnam.1.in
@@ -68,6 +68,13 @@ This can be useful for scripting.
lists the contents of a given generation, similar to
.BR "ls \-lAR" .
.IP \(bu
+.B kdirstat
+lists the contents of a given generation, in a format which is
+compatible with the
+.BR "kdirstat"
+cache file format, which can then be used to visualise the contents
+of a backup.
+.IP \(bu
.B verify
compares data in the backup with actual user data,
and makes sure they are identical.
diff --git a/obnamlib/plugins/show_plugin.py b/obnamlib/plugins/show_plugin.py
index 7c933cfb..949b2143 100644
--- a/obnamlib/plugins/show_plugin.py
+++ b/obnamlib/plugins/show_plugin.py
@@ -55,6 +55,7 @@ class ShowPlugin(obnamlib.ObnamPlugin):
self.app.add_subcommand('generations', self.generations)
self.app.add_subcommand('genids', self.genids)
self.app.add_subcommand('ls', self.ls, arg_synopsis='[FILE]...')
+ self.app.add_subcommand('kdirstat', self.kdirstat, arg_synopsis='[FILE]...')
self.app.add_subcommand('diff', self.diff,
arg_synopsis='[GENERATION1] GENERATION2')
self.app.add_subcommand('nagios-last-backup-age',
@@ -179,8 +180,8 @@ class ShowPlugin(obnamlib.ObnamPlugin):
sys.stdout.write('%s\n' % self.repo.make_generation_spec(gen_id))
self.repo.close()
- def ls(self, args):
- '''List contents of a generation.'''
+ def traverse(self, hdr, cb, args):
+ '''Traverse a generation calling callback.'''
self.open_repository()
@@ -196,12 +197,11 @@ class ShowPlugin(obnamlib.ObnamPlugin):
gen_id, obnamlib.REPO_GENERATION_ENDED)
started = self.format_time(started)
ended = self.format_time(ended)
- self.app.output.write(
- 'Generation %s (%s - %s)\n' %
+ hdr('Generation %s (%s - %s)\n' %
(self.repo.make_generation_spec(gen_id), started, ended))
- for ls_file in args:
- ls_file = self.remove_trailing_slashes(ls_file)
- self.show_objects(gen_id, ls_file)
+ for file in args:
+ file = self.remove_trailing_slashes(file)
+ self.show_objects(cb, gen_id, file)
self.repo.close()
@@ -218,19 +218,26 @@ class ShowPlugin(obnamlib.ObnamPlugin):
gen_id, filename, obnamlib.REPO_FILE_MODE)
return stat.S_ISDIR(mode)
- def show_objects(self, gen_id, dirname):
- self.show_item(gen_id, dirname)
+ def show_objects(self, cb, gen_id, dirname):
+ cb(gen_id, dirname)
subdirs = []
for filename in sorted(self.repo.get_file_children(gen_id, dirname)):
if self.isdir(gen_id, filename):
subdirs.append(filename)
else:
- self.show_item(gen_id, filename)
+ cb(gen_id, filename)
for subdir in subdirs:
- self.show_objects(gen_id, subdir)
+ self.show_objects(cb, gen_id, subdir)
+
+ def ls(self, args):
+ '''List contents of a generation.'''
+ self.traverse(self.show_hdr_ls, self.show_item_ls, args)
- def show_item(self, gen_id, filename):
+ def show_hdr_ls(self, comment):
+ self.app.output.write(comment)
+
+ def show_item_ls(self, gen_id, filename):
fields = self.fields(gen_id, filename)
widths = [
1, # mode
@@ -251,6 +258,44 @@ class ShowPlugin(obnamlib.ObnamPlugin):
result.append(fmt % (abs(widths[i]), fields[i]))
self.app.output.write('%s\n' % ' '.join(result))
+ def kdirstat(self, args):
+ '''List contents of a generation in kdirstat cache format.'''
+ self.traverse(self.show_hdr_kdirstat, self.show_item_kdirstat, args)
+
+ def show_hdr_kdirstat(self, comment):
+ self.app.output.write('''
+[kdirstat 4.0 cache file]
+# Generated by obnam %s
+# Do not edit!
+#
+# Type path size mtime <optional fields>
+
+''' % comment)
+
+ def show_item_kdirstat(self, gen_id, filename):
+ mode = self.repo.get_file_key(
+ gen_id, filename, obnamlib.REPO_FILE_MODE)
+ size = self.repo.get_file_key(
+ gen_id, filename, obnamlib.REPO_FILE_SIZE)
+ mtime_sec = self.repo.get_file_key(
+ gen_id, filename, obnamlib.REPO_FILE_MTIME_SEC)
+
+ if stat.S_ISREG(mode): mode_str = "F\t"
+ elif stat.S_ISDIR(mode): mode_str = "D "
+ elif stat.S_ISLNK(mode): mode_str = "L\t"
+ elif stat.S_ISBLK(mode): mode_str = "BlockDev\t"
+ elif stat.S_ISCHR(mode): mode_str = "CharDev\t"
+ elif stat.S_ISSOCK(mode): mode_str = "Socket\t"
+
+ enc_filename = filename.replace("%", "%25")
+ enc_filename = enc_filename.replace(" ", "%20")
+ enc_filename = enc_filename.replace("\t", "%09")
+
+ if (filename == "/"): return
+
+ self.app.output.write("%s%s\t%d\t%#x\n" %
+ (mode_str, enc_filename, size, mtime_sec))
+
def show_diff_for_file(self, gen_id, fullname, change_char):
'''Show what has changed for a single file.