Repository: borgbackup/borg

Issue:
Borg-list: Add archive size as placeholder
<!--
Thank you for reporting an issue.

*IMPORTANT* -  *before* creating a new issue please look around:
 - Borgbackup documentation: http://borgbackup.readthedocs.io/en/stable/index.html
 - FAQ: https://borgbackup.readthedocs.io/en/stable/faq.html
 and
 - open issues in Github tracker: https://github.com/borgbackup/borg/issues
  
If you cannot find a similar problem, then create a new issue.

Please fill in as much of the template as possible.
-->

Followup to #6061 

## Have you checked borgbackup docs, FAQ, and open Github issues?

Yes

## Is this a BUG / ISSUE report or a QUESTION?

ISSUE

## System information. For client/server mode post info for both machines.

borg version 1.1.17

## Describe the problem you're observing.

I would like some extra keys for repo listing:
- size of the archive files (compress/uncompressed) - ignore deduplication for this
- number of files in the archive


Discussion:
Guess you'll need some metadata for this which is (only?) available in master branch created archives since a while.

So the feature already exists but isn't published in a release yet?
I remember adding some metadata to master branch ("borg 1.2") archives a while ago.

Not sure though whether there are already placeholders that use that data, so please check.

Target branch of this ticket is only master branch (where the new metadata is present), iirc it is not in 1.1-maint.
inspired by https://github.com/borgbackup/borg/issues/6061#issuecomment-982569164
not ideal, but working
not using any additional python modules, because they are missing in https://hub.docker.com/r/b3vis/borgmatic/

```
#!/usr/bin/env python3                                                                                                                                                                                                               
# encoding: utf-8

import subprocess
import sys

def pretty_size(bytes):
	unit = "B  "
	if bytes > 999:
		bytes /= 1024.0
		unit = "KiB"
	if bytes > 999:
		bytes /= 1024.0
		unit = "MiB"
	if bytes > 999:
		bytes /= 1024.0
		unit = "GiB"
	if bytes > 999:
		bytes /= 1024.0
		unit = "TiB"
	return "{:.2f}".format(bytes) + " " + unit

repository_path = str(sys.argv[1])

list_archives_process = subprocess.run(['sh', '-c', 'borg list --format="{name}{NL}" "' + repository_path + '" | grep $(date +"%Y-%m-%d")'], capture_output=True)

for archive_string in list_archives_process.stdout.decode("utf-8").splitlines():
	read_sizes_process = subprocess.run(['sh', '-c', 'borg list ' + repository_path + '::' + archive_string + ' "--format={size},{csize},{dcsize}{NL}"'], capture_output=True)
	total_size = 0
	total_compressed_size = 0
	total_deduplicated_size = 0
	for row in read_sizes_process.stdout.decode("utf-8").splitlines():
		row_array = row.split(',')
		total_size += int(row_array[0])
		total_compressed_size += int(row_array[1])
		total_deduplicated_size += int(row_array[2])
	print ("{:<45} {:>11} total, {:>11} compressed, {:>11} deduplicated".format(archive_string, pretty_size(total_size), pretty_size(total_compressed_size), pretty_size(total_deduplicated_size)))
```

Resolving patch:
diff --git a/src/borg/helpers/parseformat.py b/src/borg/helpers/parseformat.py
--- a/src/borg/helpers/parseformat.py
+++ b/src/borg/helpers/parseformat.py
@@ -672,11 +672,14 @@ class ArchiveFormatter(BaseFormatter):
         "id": "internal ID of the archive",
         "hostname": "hostname of host on which this archive was created",
         "username": "username of user who created this archive",
+        "size": "size of this archive (data plus metadata, not considering compression and deduplication)",
+        "nfiles": "count of files in this archive",
     }
     KEY_GROUPS = (
         ("archive", "name", "comment", "id"),
         ("start", "time", "end", "command_line"),
         ("hostname", "username"),
+        ("size", "nfiles"),
     )
 
     @classmethod
@@ -722,10 +725,12 @@ def __init__(self, format, repository, manifest, key, *, json=False, iec=False):
         self.format = partial_format(format, static_keys)
         self.format_keys = {f[1] for f in Formatter().parse(format)}
         self.call_keys = {
-            "hostname": partial(self.get_meta, "hostname"),
-            "username": partial(self.get_meta, "username"),
-            "comment": partial(self.get_meta, "comment"),
-            "command_line": partial(self.get_meta, "command_line"),
+            "hostname": partial(self.get_meta, "hostname", ""),
+            "username": partial(self.get_meta, "username", ""),
+            "comment": partial(self.get_meta, "comment", ""),
+            "command_line": partial(self.get_meta, "command_line", ""),
+            "size": partial(self.get_meta, "size", 0),
+            "nfiles": partial(self.get_meta, "nfiles", 0),
             "end": self.get_ts_end,
         }
         self.used_call_keys = set(self.call_keys) & self.format_keys
@@ -771,8 +776,8 @@ def archive(self):
             self._archive = Archive(self.manifest, self.name, iec=self.iec)
         return self._archive
 
-    def get_meta(self, key):
-        return self.archive.metadata.get(key, "")
+    def get_meta(self, key, default=None):
+        return self.archive.metadata.get(key, default)
 
     def get_ts_end(self):
         return self.format_time(self.archive.ts_end)
diff --git a/src/borg/testsuite/archiver/rlist_cmd.py b/src/borg/testsuite/archiver/rlist_cmd.py
--- a/src/borg/testsuite/archiver/rlist_cmd.py
+++ b/src/borg/testsuite/archiver/rlist_cmd.py
@@ -40,6 +40,19 @@ def test_archives_format(self):
         self.assert_in("test-1 comment 1" + os.linesep, output_3)
         self.assert_in("test-2 comment 2" + os.linesep, output_3)
 
+    def test_size_nfiles(self):
+        self.cmd(f"--repo={self.repository_location}", "rcreate", RK_ENCRYPTION)
+        self.create_regular_file("file1", size=123000)
+        self.create_regular_file("file2", size=456)
+        self.cmd(f"--repo={self.repository_location}", "create", "test", "input/file1", "input/file2")
+        output = self.cmd(f"--repo={self.repository_location}", "list", "test")
+        print(output)
+        output = self.cmd(f"--repo={self.repository_location}", "rlist", "--format", "{name} {nfiles} {size}")
+        o_t = output.split()
+        assert o_t[0] == "test"
+        assert int(o_t[1]) == 2
+        assert 123456 <= int(o_t[2]) < 123999  # there is some metadata overhead
+
     def test_date_matching(self):
         self.cmd(f"--repo={self.repository_location}", "rcreate", RK_ENCRYPTION)
         earliest_ts = "2022-11-20T23:59:59"


Test patch: