diff options
author | Ian Campbell <ijc@hellion.org.uk> | 2014-11-16 11:24:11 +0000 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2014-12-06 19:02:39 +0200 |
commit | da263719bc13e65b8f5948e657a4b2555523d509 (patch) | |
tree | 39f2ac63d12cb15ca9a7b18110a31f98d15649c5 | |
parent | ce0e45fece3dcec7faace976b30d99314f2d4c15 (diff) | |
download | obnam-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.mdwn | 16 | ||||
-rw-r--r-- | obnam.1.in | 7 | ||||
-rw-r--r-- | obnamlib/plugins/show_plugin.py | 69 |
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 @@ -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. |