]> git.ipfire.org Git - thirdparty/snapper.git/commitdiff
Add ListSnapshots command
authorJosé Iván López González <jlopez@suse.com>
Wed, 30 Oct 2019 23:16:26 +0000 (23:16 +0000)
committerJosé Iván López González <jlopez@suse.com>
Wed, 30 Oct 2019 23:17:33 +0000 (23:17 +0000)
17 files changed:
client/Command/.gitignore [new file with mode: 0644]
client/Command/ColumnsOption.cc [new file with mode: 0644]
client/Command/ColumnsOption.h [new file with mode: 0644]
client/Command/ListSnapshots.cc [new file with mode: 0644]
client/Command/ListSnapshots.h [new file with mode: 0644]
client/Command/ListSnapshots/Makefile.am [new file with mode: 0644]
client/Command/ListSnapshots/Options.cc [new file with mode: 0644]
client/Command/ListSnapshots/Options.h [new file with mode: 0644]
client/Command/ListSnapshots/SnappersData.cc [new file with mode: 0644]
client/Command/ListSnapshots/SnappersData.h [new file with mode: 0644]
client/Command/ListSnapshots/SnappersData/Csv.cc [new file with mode: 0644]
client/Command/ListSnapshots/SnappersData/Csv.h [new file with mode: 0644]
client/Command/ListSnapshots/SnappersData/Json.cc [new file with mode: 0644]
client/Command/ListSnapshots/SnappersData/Json.h [new file with mode: 0644]
client/Command/ListSnapshots/SnappersData/Makefile.am [new file with mode: 0644]
client/Command/ListSnapshots/SnappersData/Table.cc [new file with mode: 0644]
client/Command/ListSnapshots/SnappersData/Table.h [new file with mode: 0644]

diff --git a/client/Command/.gitignore b/client/Command/.gitignore
new file mode 100644 (file)
index 0000000..66a3f3f
--- /dev/null
@@ -0,0 +1,2 @@
+*.lo
+*.la
diff --git a/client/Command/ColumnsOption.cc b/client/Command/ColumnsOption.cc
new file mode 100644 (file)
index 0000000..8384c00
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#include <algorithm>
+
+#include <boost/algorithm/string.hpp>
+
+#include "client/Command/ColumnsOption.h"
+#include "client/utils/text.h"
+
+using namespace std;
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       vector<string> Command::ColumnsOption::selected_columns() const
+       {
+           if (_raw_columns.empty())
+               return {};
+
+           vector<string> columns;
+
+           boost::split(columns, _raw_columns, boost::is_any_of(","), boost::token_compress_on);
+
+           return columns;
+       }
+
+
+       string Command::ColumnsOption::error() const
+       {
+           if (wrong_columns().empty())
+               return "";
+
+           return _("Unknown columns: ") + boost::algorithm::join(wrong_columns(), ", ");
+       }
+
+
+       vector<string> Command::ColumnsOption::wrong_columns() const
+       {
+           vector<string> wrong;
+
+           for (auto column : selected_columns())
+           {
+               if (wrong_column(column))
+                   wrong.push_back(column);
+           }
+
+           return wrong;
+       }
+
+
+       bool Command::ColumnsOption::wrong_column(const std::string& column) const
+       {
+           return find(_all_columns.begin(), _all_columns.end(), column) == _all_columns.end();
+       }
+
+    }
+}
diff --git a/client/Command/ColumnsOption.h b/client/Command/ColumnsOption.h
new file mode 100644 (file)
index 0000000..93bcb8d
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#ifndef SNAPPER_CLI_COMMAND_COLUMNS_OPTION_H
+#define SNAPPER_CLI_COMMAND_COLUMNS_OPTION_H
+
+#include <string>
+#include <vector>
+
+#include "client/Command.h"
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       class Command::ColumnsOption
+       {
+
+       public:
+
+           ColumnsOption(const std::vector<std::string>& all_columns) :
+               _all_columns(all_columns), _raw_columns()
+           {}
+
+           void set_raw_columns(const string& raw_columns)
+           {
+               _raw_columns = raw_columns;
+           }
+
+           std::vector<std::string> selected_columns() const;
+
+           std::string error() const;
+
+           std::vector<std::string> wrong_columns() const;
+
+       private:
+
+           bool wrong_column(const std::string& column) const;
+
+           const std::vector<std::string>& _all_columns;
+
+           std::string _raw_columns;
+
+       };
+
+    }
+}
+
+#endif
diff --git a/client/Command/ListSnapshots.cc b/client/Command/ListSnapshots.cc
new file mode 100644 (file)
index 0000000..c62d005
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#include <iostream>
+
+#include "client/utils/text.h"
+#include "client/Command/ListSnapshots.h"
+#include "client/Command/ListSnapshots/Options.h"
+#include "client/Command/ListSnapshots/SnappersData/Table.h"
+#include "client/Command/ListSnapshots/SnappersData/Csv.h"
+#include "client/Command/ListSnapshots/SnappersData/Json.h"
+
+using namespace std;
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       string Command::ListSnapshots::help()
+       {
+           return
+               string(
+                   _("  List snapshots:\n"
+                     "\tsnapper list\n"
+                     "\n")
+               ) + Options::help_text();
+       }
+
+
+       Command::ListSnapshots::ListSnapshots(const GlobalOptions& global_options,
+           GetOpts& options_parser, ProxySnappers& snappers) :
+           Command(global_options, options_parser, snappers), _options(new Options(options_parser))
+       {}
+
+
+       Command::ListSnapshots::ListSnapshots::~ListSnapshots()
+       {}
+
+
+       const Command::ListSnapshots::Options& Command::ListSnapshots::options() const
+       {
+           return *_options.get();
+       }
+
+
+       vector<string> Command::ListSnapshots::errors() const
+       {
+           return options().errors();
+       }
+
+
+       void Command::ListSnapshots::run()
+       {
+           if (_options_parser.hasArgs())
+           {
+               cerr << _("Command 'list' does not take arguments.") << endl;
+               exit(EXIT_FAILURE);
+           }
+
+           if (options().has_errors())
+           {
+               cerr << options().errors().front() << endl;
+               exit(EXIT_FAILURE);
+           }
+
+           string output;
+
+           switch (global_options().output_format())
+           {
+               case GlobalOptions::OutputFormat::TABLE:
+               {
+                   SnappersData::Table table(*this, global_options().table_style());
+                   output = table.output();
+               }
+               break;
+
+               case GlobalOptions::OutputFormat::CSV:
+               {
+                   SnappersData::Csv csv(*this, global_options().separator());
+                   output = csv.output();
+               }
+               break;
+
+               case GlobalOptions::OutputFormat::JSON:
+               {
+                   SnappersData::Json json(*this);
+                   output = json.output() + "\n";
+               }
+               break;
+           }
+
+           cout << output;
+       }
+
+    }
+}
diff --git a/client/Command/ListSnapshots.h b/client/Command/ListSnapshots.h
new file mode 100644 (file)
index 0000000..4ceaea0
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#ifndef SNAPPER_CLI_COMMAND_LIST_SNAPSHOTS_H
+#define SNAPPER_CLI_COMMAND_LIST_SNAPSHOTS_H
+
+#include <memory>
+
+#include "client/Command.h"
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       class Command::ListSnapshots : public Command
+       {
+
+       public:
+
+           class Options;
+
+           class ColumnsOption;
+
+           class SnappersData;
+
+           static std::string help();
+
+           ListSnapshots(const GlobalOptions& global_options, GetOpts& options_parser,
+               ProxySnappers& snappers);
+
+           const Options& options() const;
+
+           virtual std::vector<std::string> errors() const override;
+
+           virtual void run() override;
+
+           virtual ~ListSnapshots();
+
+       private:
+
+           std::unique_ptr<Options> _options;
+       };
+
+    }
+}
+
+#endif
diff --git a/client/Command/ListSnapshots/Makefile.am b/client/Command/ListSnapshots/Makefile.am
new file mode 100644 (file)
index 0000000..0ff5734
--- /dev/null
@@ -0,0 +1,19 @@
+#
+# Makefile.am for snapper/client/Command/ListSnapshots
+#
+
+SUBDIRS = SnappersData
+
+AM_CPPFLAGS = -I$(top_srcdir) $(DBUS_CFLAGS)
+
+noinst_LTLIBRARIES = libcommandlistsnapshots.la
+
+libcommandlistsnapshots_la_SOURCES =           \
+       Options.cc              Options.h       \
+       SnappersData.cc         SnappersData.h
+
+libcommandlistsnapshots_la_LIBADD =    \
+       ../../../snapper/libsnapper.la  \
+       ../../utils/libutils.la         \
+       ../../../dbus/libdbus.la        \
+       SnappersData/libcommandlistsnapshotssnappersdata.la
diff --git a/client/Command/ListSnapshots/Options.cc b/client/Command/ListSnapshots/Options.cc
new file mode 100644 (file)
index 0000000..c503d4e
--- /dev/null
@@ -0,0 +1,364 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#include <stdexcept>
+
+#include <boost/algorithm/string.hpp>
+
+#include "client/Command/ListSnapshots/Options.h"
+#include "client/utils/text.h"
+
+using namespace std;
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       namespace
+       {
+           const option OPTIONS[] = {
+               { "type",               required_argument,      0,      't' },
+               { "disable-used-space", no_argument,            0,      0 },
+               { "all-configs",        no_argument,            0,      'a' },
+               { "columns",            required_argument,      0,      0},
+               { 0, 0, 0, 0 }
+           };
+       }
+
+
+       string Command::ListSnapshots::Options::help_text()
+       {
+           return _("    Options for 'list' command:\n"
+                    "\t--type, -t <type>\t\tType of snapshots to list.\n"
+                    "\t--disable-used-space\t\tDisable showing used space.\n"
+                    "\t--all-configs, -a\t\tList snapshots from all accessible configs.\n"
+                    "\t--columns <columns>\t\tColumns to show separated by comma.\n"
+                    "\t\t\t\t\tPossible columns: config, subvolume, number, default, active,\n"
+                    "\t\t\t\t\ttype, date, user, used-space, cleanup, description, userdata,\n"
+                    "\t\t\t\t\tpre-number, post-number, post-date.\n");
+       }
+
+
+       const string Command::ListSnapshots::Options::Columns::CONFIG = "config";
+       const string Command::ListSnapshots::Options::Columns::SUBVOLUME = "subvolume";
+       const string Command::ListSnapshots::Options::Columns::NUMBER = "number";
+       const string Command::ListSnapshots::Options::Columns::DEFAULT = "default";
+       const string Command::ListSnapshots::Options::Columns::ACTIVE = "active";
+       const string Command::ListSnapshots::Options::Columns::TYPE = "type";
+       const string Command::ListSnapshots::Options::Columns::DATE = "date";
+       const string Command::ListSnapshots::Options::Columns::USER = "user";
+       const string Command::ListSnapshots::Options::Columns::USED_SPACE = "used-space";
+       const string Command::ListSnapshots::Options::Columns::CLEANUP = "cleanup";
+       const string Command::ListSnapshots::Options::Columns::DESCRIPTION = "description";
+       const string Command::ListSnapshots::Options::Columns::USERDATA = "userdata";
+       const string Command::ListSnapshots::Options::Columns::PRE_NUMBER = "pre-number";
+       const string Command::ListSnapshots::Options::Columns::POST_NUMBER = "post-number";
+       const string Command::ListSnapshots::Options::Columns::POST_DATE = "post-date";
+
+
+       const vector<string> Command::ListSnapshots::Options::ALL_COLUMNS = {
+           Columns::CONFIG,
+           Columns::SUBVOLUME,
+           Columns::NUMBER,
+           Columns::DEFAULT,
+           Columns::ACTIVE,
+           Columns::TYPE,
+           Columns::DATE,
+           Columns::USER,
+           Columns::USED_SPACE,
+           Columns::CLEANUP,
+           Columns::DESCRIPTION,
+           Columns::USERDATA,
+           Columns::PRE_NUMBER,
+           Columns::POST_NUMBER,
+           Columns::POST_DATE
+       };
+
+
+       Command::ListSnapshots::Options::Options(GetOpts& parser) :
+           cli::Options(parser), _columns_option(ALL_COLUMNS)
+       {
+           parse_options();
+
+           _disable_used_space = has_option("disable-used-space");
+           _all_configs = has_option("all-configs");
+           _list_mode = list_mode_value();
+
+           _columns_option.set_raw_columns(columns_raw());
+       }
+
+
+       void Command::ListSnapshots::Options::parse_options()
+       {
+           _options = _parser.parse("list", OPTIONS);
+       }
+
+
+       vector<string>
+       Command::ListSnapshots::Options::columns(GlobalOptions::OutputFormat format) const
+       {
+           if (has_option("columns"))
+               return _columns_option.selected_columns();
+
+           return list_mode_columns(format);
+       }
+
+
+       vector<string> Command::ListSnapshots::Options::errors() const
+       {
+           vector<string> detected_errors;
+
+           if (wrong_type())
+               detected_errors.push_back(type_error());
+
+           if (wrong_columns())
+               detected_errors.push_back(columns_error());
+
+           return detected_errors;
+       }
+
+
+       Command::ListSnapshots::Options::ListMode
+       Command::ListSnapshots::Options::list_mode_value() const
+       {
+           string type = has_option("type") ? get_option("type")->second : "";
+
+           if (type == "all")
+               return LM_ALL;
+
+           if (type == "single")
+               return LM_SINGLE;
+
+           if (type == "pre-post")
+               return LM_PRE_POST;
+
+           return LM_ALL;
+       }
+
+
+       string Command::ListSnapshots::Options::columns_raw() const
+       {
+           return has_option("columns") ? get_option("columns")->second : "";
+       }
+
+
+       vector<string>
+       Command::ListSnapshots::Options::list_mode_columns(GlobalOptions::OutputFormat format) const
+       {
+           vector<string> columns;
+
+           switch (list_mode())
+           {
+               case LM_ALL:
+                   columns = all_mode_columns(format);
+                   break;
+
+               case LM_SINGLE:
+                   columns = single_mode_columns(format);
+                   break;
+
+               case LM_PRE_POST:
+                   columns = pre_post_mode_columns(format);
+                   break;
+           }
+
+           if (disable_used_space())
+           {
+               columns.erase(
+                   remove(columns.begin(), columns.end(), Columns::USED_SPACE), columns.end());
+           }
+
+           return columns;
+       }
+
+
+       vector<string>
+       Command::ListSnapshots::Options::all_mode_columns(GlobalOptions::OutputFormat format) const
+       {
+           if (format == GlobalOptions::OutputFormat::TABLE)
+               return {
+                   Columns::NUMBER,
+                   Columns::TYPE,
+                   Columns::PRE_NUMBER,
+                   Columns::DATE,
+                   Columns::USER,
+                   Columns::USED_SPACE,
+                   Columns::CLEANUP,
+                   Columns::DESCRIPTION,
+                   Columns::USERDATA
+               };
+
+           if (format == GlobalOptions::OutputFormat::CSV)
+               return {
+                   Columns::CONFIG,
+                   Columns::SUBVOLUME,
+                   Columns::NUMBER,
+                   Columns::DEFAULT,
+                   Columns::ACTIVE,
+                   Columns::TYPE,
+                   Columns::PRE_NUMBER,
+                   Columns::DATE,
+                   Columns::USER,
+                   Columns::USED_SPACE,
+                   Columns::CLEANUP,
+                   Columns::DESCRIPTION,
+                   Columns::USERDATA
+               };
+
+           if (format == GlobalOptions::OutputFormat::JSON)
+               return {
+                   Columns::SUBVOLUME,
+                   Columns::NUMBER,
+                   Columns::DEFAULT,
+                   Columns::ACTIVE,
+                   Columns::TYPE,
+                   Columns::PRE_NUMBER,
+                   Columns::DATE,
+                   Columns::USER,
+                   Columns::USED_SPACE,
+                   Columns::CLEANUP,
+                   Columns::DESCRIPTION,
+                   Columns::USERDATA
+               };
+
+           throw logic_error("unknown output format");
+       }
+
+
+       vector<string>
+       Command::ListSnapshots::Options::single_mode_columns(GlobalOptions::OutputFormat format) const
+       {
+           if (format == GlobalOptions::OutputFormat::TABLE)
+               return {
+                   Columns::NUMBER,
+                   Columns::DATE,
+                   Columns::USER,
+                   Columns::USED_SPACE,
+                   Columns::DESCRIPTION,
+                   Columns::USERDATA
+               };
+
+           if (format == GlobalOptions::OutputFormat::CSV)
+               return {
+                   Columns::CONFIG,
+                   Columns::SUBVOLUME,
+                   Columns::NUMBER,
+                   Columns::DEFAULT,
+                   Columns::ACTIVE,
+                   Columns::DATE,
+                   Columns::USER,
+                   Columns::USED_SPACE,
+                   Columns::DESCRIPTION,
+                   Columns::USERDATA
+               };
+
+           if (format == GlobalOptions::OutputFormat::JSON)
+               return {
+                   Columns::SUBVOLUME,
+                   Columns::NUMBER,
+                   Columns::DEFAULT,
+                   Columns::ACTIVE,
+                   Columns::DATE,
+                   Columns::USER,
+                   Columns::USED_SPACE,
+                   Columns::DESCRIPTION,
+                   Columns::USERDATA
+               };
+
+           throw logic_error("unknown output format");
+       }
+
+
+       vector<string>
+       Command::ListSnapshots::Options::pre_post_mode_columns(GlobalOptions::OutputFormat format) const
+       {
+           if (format == GlobalOptions::OutputFormat::TABLE)
+               return {
+                   Columns::NUMBER,
+                   Columns::POST_NUMBER,
+                   Columns::DATE,
+                   Columns::POST_DATE,
+                   Columns::DESCRIPTION,
+                   Columns::USERDATA
+               };
+
+           if (format == GlobalOptions::OutputFormat::CSV)
+               return {
+                   Columns::CONFIG,
+                   Columns::SUBVOLUME,
+                   Columns::NUMBER,
+                   Columns::DEFAULT,
+                   Columns::ACTIVE,
+                   Columns::POST_NUMBER,
+                   Columns::DATE,
+                   Columns::POST_DATE,
+                   Columns::DESCRIPTION,
+                   Columns::USERDATA
+               };
+
+           if (format == GlobalOptions::OutputFormat::JSON)
+               return {
+                   Columns::SUBVOLUME,
+                   Columns::NUMBER,
+                   Columns::DEFAULT,
+                   Columns::ACTIVE,
+                   Columns::POST_NUMBER,
+                   Columns::DATE,
+                   Columns::POST_DATE,
+                   Columns::DESCRIPTION,
+                   Columns::USERDATA
+               };
+
+           throw logic_error("unknown output format");
+       }
+
+
+       bool Command::ListSnapshots::Options::wrong_type() const
+       {
+           if (!has_option("type"))
+               return false;
+
+           string type = get_option("type")->second;
+
+           return type != "all" && type != "single" && type != "pre-post";
+       }
+
+
+       bool Command::ListSnapshots::Options::wrong_columns() const
+       {
+           return !_columns_option.wrong_columns().empty();
+       }
+
+
+       string Command::ListSnapshots::Options::type_error() const
+       {
+           return _("Unknown type of snapshots.");
+       }
+
+
+       string Command::ListSnapshots::Options::columns_error() const
+       {
+           return _columns_option.error();
+       }
+
+    }
+}
diff --git a/client/Command/ListSnapshots/Options.h b/client/Command/ListSnapshots/Options.h
new file mode 100644 (file)
index 0000000..0bc1d36
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#ifndef SNAPPER_CLI_COMMAND_LIST_SNAPSHOTS_OPTIONS_H
+#define SNAPPER_CLI_COMMAND_LIST_SNAPSHOTS_OPTIONS_H
+
+#include "client/Options.h"
+#include "client/GlobalOptions.h"
+#include "client/Command/ColumnsOption.h"
+#include "client/Command/ListSnapshots.h"
+#include "client/Command/ListSnapshots/SnappersData.h"
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       class Command::ListSnapshots::Options : public cli::Options
+       {
+
+       public:
+
+           struct Columns
+           {
+               static const std::string CONFIG;
+               static const std::string SUBVOLUME;
+               static const std::string NUMBER;
+               static const std::string DEFAULT;
+               static const std::string ACTIVE;
+               static const std::string TYPE;
+               static const std::string DATE;
+               static const std::string USER;
+               static const std::string USED_SPACE;
+               static const std::string CLEANUP;
+               static const std::string DESCRIPTION;
+               static const std::string USERDATA;
+               static const std::string PRE_NUMBER;
+               static const std::string POST_NUMBER;
+               static const std::string POST_DATE;
+           };
+
+           static const std::vector<std::string> ALL_COLUMNS;
+
+           enum ListMode { LM_ALL, LM_SINGLE, LM_PRE_POST };
+
+           static std::string help_text();
+
+           Options(GetOpts& parser);
+
+           ListMode list_mode() const
+           {
+               return _list_mode;
+           }
+
+           bool disable_used_space() const
+           {
+               return _disable_used_space;
+           }
+
+           bool all_configs() const
+           {
+               return _all_configs;
+           }
+
+           std::vector<std::string> columns(GlobalOptions::OutputFormat format) const;
+
+           virtual std::vector<std::string> errors() const override;
+
+       private:
+
+           void parse_options();
+
+           ListMode list_mode_value() const;
+
+           string columns_raw() const;
+
+           std::vector<std::string> list_mode_columns(GlobalOptions::OutputFormat format) const;
+
+           std::vector<std::string> all_mode_columns(GlobalOptions::OutputFormat format) const;
+
+           std::vector<std::string> single_mode_columns(GlobalOptions::OutputFormat format) const;
+
+           std::vector<std::string> pre_post_mode_columns(GlobalOptions::OutputFormat format) const;
+
+           bool wrong_type() const;
+
+           bool wrong_columns() const;
+
+           std::string type_error() const;
+
+           std::string columns_error() const;
+
+           ListMode _list_mode;
+
+           bool _disable_used_space;
+
+           bool _all_configs;
+
+           Command::ColumnsOption _columns_option;
+
+       };
+
+    }
+}
+
+#endif
diff --git a/client/Command/ListSnapshots/SnappersData.cc b/client/Command/ListSnapshots/SnappersData.cc
new file mode 100644 (file)
index 0000000..fda2770
--- /dev/null
@@ -0,0 +1,486 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#include <algorithm>
+#include <string.h>
+#include <time.h>
+#include <dbus/DBusMessage.h>
+
+#include <snapper/Snapper.h>
+#include <snapper/Exception.h>
+#include <snapper/SnapperTmpl.h>
+#include <snapper/Snapshot.h>
+#include <snapper/Enum.h>
+
+#include "client/Command/ListSnapshots/SnappersData.h"
+#include "client/Command/ListSnapshots/Options.h"
+#include "client/utils/HumanString.h"
+#include "client/misc.h"
+
+using namespace std;
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       Command::ListSnapshots::SnappersData::SnappersData(const Command::ListSnapshots& command) :
+           _command(command)
+       {
+           calculate_used_space();
+       }
+
+
+       Command::ListSnapshots::SnappersData::~SnappersData()
+       {}
+
+
+       vector<ProxySnapper*> Command::ListSnapshots::SnappersData::snappers() const
+       {
+           vector<ProxySnapper*> snappers;
+
+           for (auto config_name : configs())
+           {
+               ProxySnapper* snapper = command().snappers().getSnapper(config_name);
+
+               snappers.push_back(snapper);
+           }
+
+           return snappers;
+       }
+
+
+       vector<const ProxySnapshot*>
+       Command::ListSnapshots::SnappersData::selected_snapshots(const ProxySnapper* snapper) const
+       {
+           const ProxySnapshots& snapshots = this->snapshots(snapper);
+
+           vector<const ProxySnapshot*> selected;
+
+           for (const ProxySnapshot& snapshot : snapshots)
+           {
+               switch (command().options().list_mode())
+               {
+                   case Options::ListMode::LM_ALL:
+                   {
+                       selected.push_back(&snapshot);
+                   }
+                   break;
+
+                   case Options::ListMode::LM_SINGLE:
+                   {
+                       if (snapshot.getType() == SINGLE)
+                           selected.push_back(&snapshot);
+                   }
+                   break;
+
+                   case Options::ListMode::LM_PRE_POST:
+                   {
+                       ProxySnapshots::const_iterator post_it = find_post(snapshot, snapshots);
+
+                       if (post_it != snapshots.end())
+                           selected.push_back(&snapshot);
+                   }
+                   break;
+               }
+           }
+
+           return selected;
+       }
+
+
+       vector<string>
+       Command::ListSnapshots::SnappersData::selected_columns() const
+       {
+           return command().options().columns(command().global_options().output_format());
+       }
+
+
+       string Command::ListSnapshots::SnappersData::value_for(const string& column,
+           const ProxySnapshot& snapshot,
+           const ProxySnapshots& snapshots,
+           const ProxySnapper* snapper) const
+       {
+           if (column == Options::Columns::CONFIG)
+               return config_value(snapper);
+
+           if (column == Options::Columns::SUBVOLUME)
+               return subvolume_value(snapper);
+
+           if (column == Options::Columns::NUMBER)
+               return number_value(snapshot, snapshots);
+
+           if (column == Options::Columns::DEFAULT)
+               return default_value(snapshot, snapshots);
+
+           if (column == Options::Columns::ACTIVE)
+               return active_value(snapshot, snapshots);
+
+           if (column == Options::Columns::TYPE)
+               return type_value(snapshot);
+
+           if (column == Options::Columns::DATE)
+               return date_value(snapshot);
+
+           if (column == Options::Columns::USER)
+               return user_value(snapshot);
+
+           if (column == Options::Columns::USED_SPACE)
+               return used_space_value(snapshot, snapper);
+
+           if (column == Options::Columns::CLEANUP)
+               return cleanup_value(snapshot);
+
+           if (column == Options::Columns::DESCRIPTION)
+               return description_value(snapshot);
+
+           if (column == Options::Columns::USERDATA)
+               return userdata_value(snapshot);
+
+           if (column == Options::Columns::PRE_NUMBER)
+               return pre_number_value(snapshot);
+
+           if (column == Options::Columns::POST_NUMBER)
+               return post_number_value(snapshot, snapshots);
+
+           if (column == Options::Columns::POST_DATE)
+               return post_date_value(snapshot, snapshots);
+
+           return "";
+       }
+
+
+       vector<string> Command::ListSnapshots::SnappersData::configs() const
+       {
+           vector<string> names;
+
+           if (!command().options().all_configs())
+           {
+               names.push_back(command().global_options().config());
+           }
+           else
+           {
+               map<string, ProxyConfig> configs = command().snappers().getConfigs();
+
+               for (map<string, ProxyConfig>::value_type it : configs)
+                   names.push_back(it.first);
+           }
+
+           return names;
+       }
+
+
+       const ProxySnapshots&
+       Command::ListSnapshots::SnappersData::snapshots(const ProxySnapper* snapper) const
+       {
+           return snapper->getSnapshots();
+       }
+
+
+       bool
+       Command::ListSnapshots::SnappersData::calculated_used_space(const ProxySnapper* snapper) const
+       {
+           map<const ProxySnapper*, bool>::const_iterator it = _calculated_used_space.find(snapper);
+
+           if (it == _calculated_used_space.end())
+               return false;
+
+           return it->second;
+       }
+
+
+       void Command::ListSnapshots::SnappersData::calculate_used_space()
+       {
+           for (ProxySnapper* snapper : snappers())
+               calculate_used_space(snapper);
+       }
+
+
+       void
+       Command::ListSnapshots::SnappersData::calculate_used_space(ProxySnapper* snapper)
+       {
+           bool calculated;
+
+           if (!need_used_space())
+           {
+               calculated = false;
+           }
+           else
+           {
+               try
+               {
+                   snapper->calculateUsedSpace();
+
+                   calculated = true;
+               }
+               catch (const QuotaException& e)
+               {
+                   SN_CAUGHT(e);
+
+                   calculated = false;
+               }
+           }
+
+           _calculated_used_space[snapper] = calculated;
+       }
+
+
+       bool Command::ListSnapshots::SnappersData::need_used_space() const
+       {
+           vector<string> columns = selected_columns();
+
+           vector<string>::iterator it =
+               find(columns.begin(), columns.end(), Options::Columns::USED_SPACE);
+
+           return it != columns.end();
+       }
+
+
+       string
+       Command::ListSnapshots::SnappersData::config_value(const ProxySnapper* snapper) const
+       {
+           return snapper->configName();
+       }
+
+
+       string
+       Command::ListSnapshots::SnappersData::subvolume_value(const ProxySnapper* snapper) const
+       {
+           return snapper->getConfig().getSubvolume();
+       }
+
+
+       string
+       Command::ListSnapshots::SnappersData::number_value(const ProxySnapshot& snapshot,
+           const ProxySnapshots& snapshots) const
+       {
+           return decString(snapshot.getNum());
+       }
+
+
+       string Command::ListSnapshots::SnappersData::default_value(const ProxySnapshot& snapshot,
+           const ProxySnapshots& snapshots) const
+       {
+           return boolean_text(is_default_snapshot(snapshot, snapshots));
+       }
+
+
+       string Command::ListSnapshots::SnappersData::active_value(const ProxySnapshot& snapshot,
+           const ProxySnapshots& snapshots) const
+       {
+           return boolean_text(is_active_snapshot(snapshot, snapshots));
+       }
+
+
+       string
+       Command::ListSnapshots::SnappersData::date_value(const ProxySnapshot& snapshot) const
+       {
+           if (command().options().list_mode() == Options::ListMode::LM_PRE_POST)
+               return snapshot_date(snapshot);
+
+           return snapshot.isCurrent() ? "" : snapshot_date(snapshot);
+       }
+
+       string
+       Command::ListSnapshots::SnappersData::type_value(const ProxySnapshot& snapshot) const
+       {
+           return toString(snapshot.getType());
+       }
+
+
+       string
+       Command::ListSnapshots::SnappersData::user_value(const ProxySnapshot& snapshot) const
+       {
+           return username(snapshot.getUid());
+       }
+
+
+       string Command::ListSnapshots::SnappersData::used_space_value(
+           const ProxySnapshot& snapshot, const ProxySnapper* snapper) const
+       {
+           if (!calculated_used_space(snapper))
+               return "";
+
+           return snapshot.isCurrent() ? "" : snapshot_used_value(snapshot);
+       }
+
+
+       string
+       Command::ListSnapshots::SnappersData::cleanup_value(const ProxySnapshot& snapshot) const
+       {
+           return snapshot.getCleanup();
+       }
+
+
+       string
+       Command::ListSnapshots::SnappersData::description_value(const ProxySnapshot& snapshot) const
+       {
+           return snapshot.getDescription();
+       }
+
+
+       string
+       Command::ListSnapshots::SnappersData::userdata_value(const ProxySnapshot& snapshot) const
+       {
+           return show_userdata(snapshot.getUserdata());
+       }
+
+
+       string
+       Command::ListSnapshots::SnappersData::pre_number_value(const ProxySnapshot& snapshot) const
+       {
+           return snapshot.getType() == POST ? decString(snapshot.getPreNum()) : "";
+       }
+
+
+       string
+       Command::ListSnapshots::SnappersData::post_number_value(const ProxySnapshot& snapshot,
+           const ProxySnapshots& snapshots) const
+       {
+           ProxySnapshots::const_iterator post_it = find_post(snapshot, snapshots);
+
+           if (post_it == snapshots.end())
+               return "";
+
+           return number_value(*post_it, snapshots);
+       }
+
+
+       string
+       Command::ListSnapshots::SnappersData::post_date_value(const ProxySnapshot& snapshot,
+           const ProxySnapshots& snapshots) const
+       {
+           ProxySnapshots::const_iterator post_it = find_post(snapshot, snapshots);
+
+           if (post_it == snapshots.end())
+               return "";
+
+           return snapshot_date(*post_it);
+       }
+
+
+       string
+       Command::ListSnapshots::SnappersData::snapshot_date(const ProxySnapshot& snapshot) const
+       {
+           bool iso = command().global_options().iso();
+
+           if (command().global_options().output_format() != GlobalOptions::OutputFormat::TABLE)
+               iso = true;
+
+           return datetime(snapshot.getDate(), command().global_options().utc(), iso);
+       }
+
+
+       string
+       Command::ListSnapshots::SnappersData::snapshot_used_value(const ProxySnapshot& snapshot) const
+       {
+           uint64_t used_space = snapshot.getUsedSpace();
+
+           if (command().global_options().output_format() == GlobalOptions::OutputFormat::TABLE)
+               return byte_to_humanstring(used_space, 2);
+           else
+               return to_string(used_space);
+       }
+
+
+       bool
+       Command::ListSnapshots::SnappersData::is_default_snapshot(const ProxySnapshot& snapshot,
+           const ProxySnapshots& snapshots) const
+       {
+           auto default_snapshot = this->default_snapshot(snapshots);
+
+           return default_snapshot != snapshots.end() &&
+               default_snapshot->getNum() == snapshot.getNum();
+       }
+
+
+       bool
+       Command::ListSnapshots::SnappersData::is_active_snapshot(const ProxySnapshot& snapshot,
+           const ProxySnapshots& snapshots) const
+       {
+           auto active_snapshot = this->active_snapshot(snapshots);
+
+           return active_snapshot != snapshots.end() &&
+               active_snapshot->getNum() == snapshot.getNum();
+       }
+
+
+       ProxySnapshots::const_iterator
+       Command::ListSnapshots::SnappersData::default_snapshot(const ProxySnapshots& snapshots) const
+       {
+           try
+           {
+               return snapshots.getDefault();
+           }
+           catch (const DBus::ErrorException& e)
+           {
+               SN_CAUGHT(e);
+
+               // If snapper was just updated and the old snapperd is still
+               // running it might not know the GetDefaultSnapshot method.
+
+               if (strcmp(e.name(), "error.unknown_method") != 0)
+                   SN_RETHROW(e);
+
+               return snapshots.end();
+           }
+       }
+
+
+       ProxySnapshots::const_iterator
+       Command::ListSnapshots::SnappersData::active_snapshot(const ProxySnapshots& snapshots) const
+       {
+           try
+           {
+               return snapshots.getActive();
+           }
+           catch (const DBus::ErrorException& e)
+           {
+               SN_CAUGHT(e);
+
+               // If snapper was just updated and the old snapperd is still
+               // running it might not know the GetActiveSnapshot method.
+
+               if (strcmp(e.name(), "error.unknown_method") != 0)
+                   SN_RETHROW(e);
+
+               return snapshots.end();
+           }
+       }
+
+
+       ProxySnapshots::const_iterator Command::ListSnapshots::SnappersData::find_post(
+           const ProxySnapshot& snapshot, const ProxySnapshots& snapshots) const
+       {
+           if (snapshot.getType() != SnapshotType::PRE)
+               return snapshots.end();
+
+           ProxySnapshots::const_iterator it = snapshots.find(snapshot.getNum());
+           ProxySnapshots::const_iterator post_it = snapshots.findPost(it);
+
+           if (post_it == snapshots.end())
+               return snapshots.end();
+
+           return post_it;
+       }
+
+    }
+}
diff --git a/client/Command/ListSnapshots/SnappersData.h b/client/Command/ListSnapshots/SnappersData.h
new file mode 100644 (file)
index 0000000..204eca5
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#ifndef SNAPPER_CLI_COMMAND_LIST_SNAPSHOTS_SNAPPERS_DATA_H
+#define SNAPPER_CLI_COMMAND_LIST_SNAPSHOTS_SNAPPERS_DATA_H
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "client/Command/ListSnapshots.h"
+#include "client/proxy.h"
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       class Command::ListSnapshots::SnappersData
+       {
+
+       public:
+
+           class Table;
+
+           class Csv;
+
+           class Json;
+
+           SnappersData(const Command::ListSnapshots& command);
+
+           virtual ~SnappersData();
+
+           virtual std::string output() const = 0;
+
+       protected:
+
+           const ListSnapshots& command() const
+           {
+               return _command;
+           }
+
+           std::vector<ProxySnapper*> snappers() const;
+
+           vector<const ProxySnapshot*> selected_snapshots(const ProxySnapper* snapper) const;
+
+           std::vector<std::string> selected_columns() const;
+
+           std::string value_for(const string& column,
+               const ProxySnapshot& snapshot, const ProxySnapshots& snapshots,
+               const ProxySnapper* snapper) const;
+
+       private:
+
+           std::vector<std::string> configs() const;
+
+           const ProxySnapshots& snapshots(const ProxySnapper* snapper) const;
+
+           bool calculated_used_space(const ProxySnapper* snapper) const;
+
+           void calculate_used_space();
+
+           void calculate_used_space(ProxySnapper* snapper);
+
+           bool need_used_space() const;
+
+           std::string config_value(const ProxySnapper* snapper) const;
+
+           std::string subvolume_value(const ProxySnapper* snapper) const;
+
+           virtual std::string
+           number_value(const ProxySnapshot& snapshot, const ProxySnapshots& snapshots) const;
+
+           std::string
+           default_value(const ProxySnapshot& snapshot, const ProxySnapshots& snapshots) const;
+
+           std::string
+           active_value(const ProxySnapshot& snapshot, const ProxySnapshots& snapshots) const;
+
+           std::string type_value(const ProxySnapshot& snapshot) const;
+
+           std::string date_value(const ProxySnapshot& snapshot) const;
+
+           std::string user_value(const ProxySnapshot& snapshot) const;
+
+           std::string
+           used_space_value(const ProxySnapshot& snapshot, const ProxySnapper* snapper) const;
+
+           std::string cleanup_value(const ProxySnapshot& snapshot) const;
+
+           std::string description_value(const ProxySnapshot& snapshot) const;
+
+           std::string userdata_value(const ProxySnapshot& snapshot) const;
+
+           std::string pre_number_value(const ProxySnapshot& snapshot) const;
+
+           std::string post_number_value(const ProxySnapshot& snapshot,
+               const ProxySnapshots& snapshots) const;
+
+           std::string
+           post_date_value(const ProxySnapshot& snapshot, const ProxySnapshots& snapshots) const;
+
+           std::string snapshot_date(const ProxySnapshot& snapshot) const;
+
+           std::string snapshot_used_value(const ProxySnapshot& snapshot) const;
+
+           bool
+           is_default_snapshot(const ProxySnapshot& snapshot, const ProxySnapshots& snapshots) const;
+
+           bool
+           is_active_snapshot(const ProxySnapshot& snapshot, const ProxySnapshots& snapshots) const;
+
+           ProxySnapshots::const_iterator default_snapshot(const ProxySnapshots& snapshots) const;
+
+           ProxySnapshots::const_iterator active_snapshot(const ProxySnapshots& snapshots) const;
+
+           ProxySnapshots::const_iterator
+           find_post(const ProxySnapshot& snapshot, const ProxySnapshots& snapshots) const;
+
+           virtual string boolean_text(bool value) const = 0;
+
+           const ListSnapshots& _command;
+
+           std::map<const ProxySnapper*, bool> _calculated_used_space;
+
+       };
+
+    }
+}
+
+#endif
diff --git a/client/Command/ListSnapshots/SnappersData/Csv.cc b/client/Command/ListSnapshots/SnappersData/Csv.cc
new file mode 100644 (file)
index 0000000..9be1ba6
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#include <snapper/AppUtil.h>
+
+#include "client/Command/ListSnapshots/SnappersData/Csv.h"
+#include "client/utils/CsvFormatter.h"
+
+using namespace std;
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       Command::ListSnapshots::SnappersData::Csv::Csv(
+           const Command::ListSnapshots& command, const string separator) :
+          SnappersData(command), _separator(separator)
+       {}
+
+
+       std::string Command::ListSnapshots::SnappersData::Csv::output() const
+       {
+           vector<string> columns;
+
+           vector<vector<string>> rows;
+
+           for (const string& column : selected_columns())
+               columns.push_back(column);
+
+           for (ProxySnapper* snapper : snappers())
+           {
+               vector<vector<string>> snapper_rows = this->snapper_rows(snapper);
+
+               rows.insert(rows.end(), snapper_rows.begin(), snapper_rows.end());
+           }
+
+           CsvFormatter formatter(columns, rows, _separator);
+
+           return formatter.output();
+       }
+
+
+       vector<vector<string>>
+       Command::ListSnapshots::SnappersData::Csv::snapper_rows(const ProxySnapper* snapper) const
+       {
+           vector<vector<string>> rows;
+
+           const ProxySnapshots& snapshots = this->snapshots(snapper);
+
+           for (const ProxySnapshot* snapshot : selected_snapshots(snapper))
+           {
+               vector<string> row;
+
+               for (const string& column : selected_columns())
+               {
+                   row.push_back(value_for(column, *snapshot, snapshots, snapper));
+               }
+
+               rows.push_back(row);
+           }
+
+           return rows;
+       }
+
+
+       string Command::ListSnapshots::SnappersData::Csv::boolean_text(bool value) const
+       {
+           return value ? "yes" : "no";
+       }
+
+    }
+}
diff --git a/client/Command/ListSnapshots/SnappersData/Csv.h b/client/Command/ListSnapshots/SnappersData/Csv.h
new file mode 100644 (file)
index 0000000..5942191
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#ifndef SNAPPER_CLI_COMMAND_LIST_SNAPSHOTS_SNAPPERS_DATA_CSV_H
+#define SNAPPER_CLI_COMMAND_LIST_SNAPSHOTS_SNAPPERS_DATA_CSV_H
+
+#include <vector>
+#include <string>
+
+#include "client/Command/ListSnapshots/SnappersData.h"
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       class Command::ListSnapshots::SnappersData::Csv : public Command::ListSnapshots::SnappersData
+       {
+
+       public:
+
+           Csv(const ListSnapshots& command, const std::string separator);
+
+           virtual std::string output() const override;
+
+       private:
+
+           std::vector<std::vector<std::string>> snapper_rows(const ProxySnapper* snapper) const;
+
+           virtual string boolean_text(bool value) const override;
+
+           const std::string _separator;
+       };
+
+    }
+}
+
+#endif
diff --git a/client/Command/ListSnapshots/SnappersData/Json.cc b/client/Command/ListSnapshots/SnappersData/Json.cc
new file mode 100644 (file)
index 0000000..582a716
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#include <utility>
+
+#include "client/Command/ListSnapshots/SnappersData/Json.h"
+#include "client/Command/ListSnapshots/Options.h"
+#include "client/utils/JsonFormatter.h"
+
+using namespace std;
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       namespace
+       {
+
+           string userdata_json(const map<string, string>& userdata)
+           {
+               if (userdata.empty())
+                   return "";
+
+               JsonFormatter::Data data;
+
+               for (auto udata : userdata)
+                   data.emplace_back(udata.first, udata.second);
+
+               JsonFormatter formatter(data);
+
+               formatter.set_inline(true);
+
+               return formatter.output(3);
+           }
+
+       }
+
+
+       std::string Command::ListSnapshots::SnappersData::Json::output() const
+       {
+           JsonFormatter::Data data;
+
+           vector<string> keys;
+
+           for (ProxySnapper* snapper : snappers())
+           {
+               data.emplace_back(snapper_key(snapper), snapper_value(snapper));
+
+               keys.push_back(snapper_key(snapper));
+           }
+
+           JsonFormatter formatter(data);
+
+           formatter.skip_format_values(keys);
+
+           return formatter.output();
+       }
+
+
+       string
+       Command::ListSnapshots::SnappersData::Json::snapper_key(const ProxySnapper* snapper) const
+       {
+           return snapper->configName();
+       }
+
+
+       string
+       Command::ListSnapshots::SnappersData::Json::snapper_value(const ProxySnapper* snapper) const
+       {
+           vector<string> data;
+
+           const ProxySnapshots& snapshots = this->snapshots(snapper);
+
+           for (const ProxySnapshot* snapshot : selected_snapshots(snapper))
+           {
+               JsonFormatter::Data snapshot_data;
+
+               vector<string> skip_format;
+
+               for (const string& column : selected_columns())
+               {
+                   string value = value_for(column, *snapshot, snapshots, snapper);
+
+                   if (column == Options::Columns::USERDATA)
+                       value = userdata_json(snapshot->getUserdata());
+
+                   if (value.empty())
+                   {
+                       value = "null";
+                       skip_format.push_back(column);
+                   }
+
+                   if (skip_format_value(column))
+                       skip_format.push_back(column);
+
+                   snapshot_data.emplace_back(column, value);
+               }
+
+               JsonFormatter formatter(snapshot_data);
+
+               formatter.skip_format_values(skip_format);
+
+               data.push_back(formatter.output(2));
+           }
+
+           JsonFormatter::List json_list(data);
+
+           return json_list.output(1);
+       }
+
+
+       bool Command::ListSnapshots::SnappersData::Json::skip_format_value(const string& column) const
+       {
+           if (column == Options::Columns::NUMBER ||
+               column == Options::Columns::DEFAULT ||
+               column == Options::Columns::ACTIVE ||
+               column == Options::Columns::USED_SPACE ||
+               column == Options::Columns::USERDATA ||
+               column == Options::Columns::PRE_NUMBER ||
+               column == Options::Columns::POST_NUMBER)
+               return true;
+           else
+               return false;
+       }
+
+
+       string Command::ListSnapshots::SnappersData::Json::boolean_text(bool value) const
+       {
+           return value ? "true" : "false";
+       }
+
+    }
+}
diff --git a/client/Command/ListSnapshots/SnappersData/Json.h b/client/Command/ListSnapshots/SnappersData/Json.h
new file mode 100644 (file)
index 0000000..9578621
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#ifndef SNAPPER_CLI_COMMAND_LIST_SNAPSHOTS_SNAPPERS_DATA_JSON_H
+#define SNAPPER_CLI_COMMAND_LIST_SNAPSHOTS_SNAPPERS_DATA_JSON_H
+
+#include <vector>
+#include <string>
+
+#include "client/Command/ListSnapshots/SnappersData.h"
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       class Command::ListSnapshots::SnappersData::Json : public Command::ListSnapshots::SnappersData
+       {
+
+       public:
+
+           using Command::ListSnapshots::SnappersData::SnappersData;
+
+           virtual std::string output() const override;
+
+       private:
+
+           std::string snapper_key(const ProxySnapper* snapper) const;
+
+           std::string snapper_value(const ProxySnapper* snapper) const;
+
+           std::string snapshot_json(const ProxySnapshot* snapshot) const;
+
+           bool skip_format_value(const string& column) const;
+
+           virtual string boolean_text(bool value) const override;
+
+       };
+
+    }
+}
+
+#endif
diff --git a/client/Command/ListSnapshots/SnappersData/Makefile.am b/client/Command/ListSnapshots/SnappersData/Makefile.am
new file mode 100644 (file)
index 0000000..41ebdb2
--- /dev/null
@@ -0,0 +1,17 @@
+#
+# Makefile.am for snapper/client/Command/ListSnapshots/SnappersData
+#
+
+AM_CPPFLAGS = -I$(top_srcdir) $(DBUS_CFLAGS)
+
+noinst_LTLIBRARIES = libcommandlistsnapshotssnappersdata.la
+
+libcommandlistsnapshotssnappersdata_la_SOURCES =       \
+       Table.cc        Table.h                         \
+       Csv.cc          Csv.h                           \
+       Json.cc         Json.h
+
+libcommandlistsnapshotssnappersdata_la_LIBADD =                \
+       ../../../../snapper/libsnapper.la               \
+       ../../../utils/libutils.la                      \
+       ../../../../dbus/libdbus.la
diff --git a/client/Command/ListSnapshots/SnappersData/Table.cc b/client/Command/ListSnapshots/SnappersData/Table.cc
new file mode 100644 (file)
index 0000000..424214c
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#include <utility>
+
+#include <snapper/AppUtil.h>
+
+#include "client/Command/ListSnapshots/SnappersData/Table.h"
+#include "client/Command/ListSnapshots/Options.h"
+#include "client/utils/TableFormatter.h"
+#include "client/utils/text.h"
+
+using namespace std;
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       Command::ListSnapshots::SnappersData::Table::Table(
+           const Command::ListSnapshots& command, TableLineStyle style) :
+           SnappersData(command), _style(style)
+       {}
+
+
+       string Command::ListSnapshots::SnappersData::Table::output() const
+       {
+           string output;
+
+           vector<ProxySnapper*> snappers = this->snappers();
+
+           for (ProxySnapper* snapper : snappers)
+           {
+               if (snappers.size() > 1)
+               {
+                   if (snapper != *snappers.begin())
+                       output += "\n";
+
+                   output += snapper_description(snapper) + "\n";
+               }
+
+               output += snapper_table(snapper);
+           }
+
+           return output;
+       }
+
+
+       string Command::ListSnapshots::SnappersData::Table::label_for(const string& column) const
+       {
+           Options::ListMode list_mode = command().options().list_mode();
+
+           if (column == Options::Columns::CONFIG)
+               return _("Config");
+
+           if (column == Options::Columns::SUBVOLUME)
+               return _("Subvolume");
+
+           if (column == Options::Columns::NUMBER)
+               return list_mode == Options::LM_PRE_POST ? _("Pre #") : _("#");
+
+           if (column == Options::Columns::DEFAULT)
+               return _("Default");
+
+           if (column == Options::Columns::ACTIVE)
+               return _("Active");
+
+           if (column == Options::Columns::TYPE)
+               return _("Type");
+
+           if (column == Options::Columns::DATE)
+               return list_mode == Options::LM_PRE_POST ? _("Pre Date") : _("Date");
+
+           if (column == Options::Columns::USER)
+               return _("User");
+
+           if (column == Options::Columns::USED_SPACE)
+               return _("Used Space");
+
+           if (column == Options::Columns::CLEANUP)
+               return _("Cleanup");
+
+           if (column == Options::Columns::DESCRIPTION)
+               return _("Description");
+
+           if (column == Options::Columns::USERDATA)
+               return _("Userdata");
+
+           if (column == Options::Columns::PRE_NUMBER)
+               return _("Pre #");
+
+           if (column == Options::Columns::POST_NUMBER)
+               return _("Post #");
+
+           if (column == Options::Columns::POST_DATE)
+               return _("Post Date");
+
+           return "";
+       }
+
+
+       string Command::ListSnapshots::SnappersData::Table::number_value(const ProxySnapshot& snapshot,
+           const ProxySnapshots& snapshots) const
+       {
+           bool is_default = is_default_snapshot(snapshot, snapshots);
+           bool is_active = is_active_snapshot(snapshot, snapshots);
+
+           static const char sign[2][2] = { { ' ', '-' }, { '+', '*' } };
+
+           return Command::ListSnapshots::SnappersData::number_value(snapshot, snapshots) +
+               sign[is_default][is_active];
+       }
+
+
+       string Command::ListSnapshots::SnappersData::Table::boolean_text(bool value) const
+       {
+           return value ? _("yes") : _("no");
+       }
+
+
+       string Command::ListSnapshots::SnappersData::Table::snapper_description(
+           const ProxySnapper* snapper) const
+       {
+           return sformat(_("Config: %s, subvolume: %s"), config_value(snapper).c_str(),
+               subvolume_value(snapper).c_str());
+       }
+
+
+       string Command::ListSnapshots::SnappersData::Table::snapper_table(
+           const ProxySnapper* snapper) const
+       {
+           const ProxySnapshots& snapshots = this->snapshots(snapper);
+
+           vector<pair<string, TableAlign>> columns;
+
+           vector<vector<string>> rows;
+
+           for (const string& column : selected_columns())
+           {
+               if (skip_column(column, snapper))
+                   continue;
+
+               columns.emplace_back(label_for(column), column_alignment(column));
+           }
+
+           for (const ProxySnapshot* snapshot : selected_snapshots(snapper))
+           {
+               vector<string> row;
+
+               for (const string& column : selected_columns())
+               {
+                   if (skip_column(column, snapper))
+                       continue;
+
+                   row.push_back(value_for(column, *snapshot, snapshots, snapper));
+               }
+
+               rows.push_back(row);
+           }
+
+           TableFormatter formatter(columns, rows, _style);
+
+           return formatter.output();
+       }
+
+
+       bool Command::ListSnapshots::SnappersData::Table::skip_column(const string& column,
+           const ProxySnapper* snapper) const
+       {
+           return  column == Options::Columns::USED_SPACE && !calculated_used_space(snapper);
+       }
+
+
+       TableAlign
+       Command::ListSnapshots::SnappersData::Table::column_alignment(const string& column) const
+       {
+           if (column == Options::Columns::NUMBER ||
+               column == Options::Columns::PRE_NUMBER ||
+               column == Options::Columns::POST_NUMBER ||
+               column == Options::Columns::USED_SPACE)
+               return TableAlign::RIGHT;
+           else
+               return TableAlign::LEFT;
+       }
+
+    }
+}
diff --git a/client/Command/ListSnapshots/SnappersData/Table.h b/client/Command/ListSnapshots/SnappersData/Table.h
new file mode 100644 (file)
index 0000000..b32d3ce
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#ifndef SNAPPER_CLI_COMMAND_LIST_SNAPSHOTS_SNAPPERS_DATA_TABLE_H
+#define SNAPPER_CLI_COMMAND_LIST_SNAPSHOTS_SNAPPERS_DATA_TABLE_H
+
+#include <string>
+
+#include "client/Command/ListSnapshots/SnappersData.h"
+#include "client/utils/Table.h"
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       class Command::ListSnapshots::SnappersData::Table : public Command::ListSnapshots::SnappersData
+       {
+
+       public:
+
+           Table(const ListSnapshots& command, TableLineStyle style);
+
+           virtual std::string output() const override;
+
+       private:
+
+           std::string label_for(const string& column) const;
+
+           virtual std::string
+           number_value(const ProxySnapshot& snapshot, const ProxySnapshots& snapshots) const override;
+
+           virtual string boolean_text(bool value) const override;
+
+           std::string snapper_description(const ProxySnapper* snapper) const;
+
+           std::string snapper_table(const ProxySnapper* snapper) const;
+
+           bool skip_column(const string& column, const ProxySnapper* snapper) const;
+
+           TableAlign column_alignment(const string& column) const;
+
+           TableLineStyle _style;
+
+       };
+
+    }
+}
+
+#endif