From: Arvin Schnell Date: Thu, 26 Apr 2012 13:48:42 +0000 (+0200) Subject: - started work on DBus interface X-Git-Tag: v0.1.3~238 X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=14e2077e2cab4eaca5ff77c79b1b64e32e7131ca;p=thirdparty%2Fsnapper.git - started work on DBus interface --- diff --git a/Makefile.am b/Makefile.am index e4080fa7..a6cae224 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,7 +2,7 @@ # Makefile.am for snapper # -SUBDIRS = snapper examples tools scripts data doc po testsuite-real bindings +SUBDIRS = snapper examples tools dbus server client scripts data doc po testsuite-real bindings AUTOMAKE_OPTIONS = foreign dist-bzip2 no-dist-gzip diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 00000000..75c800d7 --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,2 @@ +*.o +snapper diff --git a/client/Makefile.am b/client/Makefile.am new file mode 100644 index 00000000..7c049593 --- /dev/null +++ b/client/Makefile.am @@ -0,0 +1,12 @@ +# +# Makefile.am for snapper/tools/client +# + +INCLUDES = -I$(top_srcdir) -I/usr/include/dbus-1.0 -I/usr/lib/dbus-1.0/include + + +bin_PROGRAMS = snapper + +snapper_SOURCES = snapper.cc types.cc commands.cc +snapper_LDADD = ../snapper/libsnapper.la ../tools/utils/libutils.la ../dbus/libdbus.la -ldbus-1 + diff --git a/client/commands.cc b/client/commands.cc new file mode 100644 index 00000000..e1a688ff --- /dev/null +++ b/client/commands.cc @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2012 Novell, Inc. + * + * 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 +#include +#include +#include +#include + +#include "commands.h" + + +list +command_list_xconfigs(DBus::Connection& conn) +{ + DBus::MessageMethodCall call("org.opensuse.snapper", "/org/opensuse/snapper", + "org.opensuse.snapper", "ListConfigs"); + + DBus::Message reply = conn.send_and_reply_and_block(call); + + list ret; + + DBus::Hihi hihi(reply); + hihi >> ret; + + return ret; +} + + +list +command_list_xsnapshots(DBus::Connection& conn, const string& config_name) +{ + DBus::MessageMethodCall call("org.opensuse.snapper", "/org/opensuse/snapper", + "org.opensuse.snapper", "ListSnapshots"); + + DBus::Hoho hoho(call); + hoho << config_name; + + DBus::Message reply = conn.send_and_reply_and_block(call); + + list ret; + + DBus::Hihi hihi(reply); + hihi >> ret; + + return ret; +} + + +unsigned int +command_create_xsnapshot(DBus::Connection& conn, const string& config_name, XSnapshotType type, + unsigned int prenum, const string& description, const string& cleanup, + const map& userdata) +{ + DBus::MessageMethodCall call("org.opensuse.snapper", "/org/opensuse/snapper", + "org.opensuse.snapper", "CreateSnapshot"); + + DBus::Hoho hoho(call); + hoho << config_name << type << prenum << description << cleanup << userdata; + + DBus::Message reply = conn.send_and_reply_and_block(call); + + unsigned int number; + + DBus::Hihi hihi(reply); + hihi >> number; + + return number; +} diff --git a/client/commands.h b/client/commands.h new file mode 100644 index 00000000..dbb16cc8 --- /dev/null +++ b/client/commands.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2012 Novell, Inc. + * + * 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 +#include +#include +#include +#include + +#include +#include +#include + +using std::string; +using std::list; +using std::map; + +#include "types.h" + + +list +command_list_xconfigs(DBus::Connection& conn); + +list +command_list_xsnapshots(DBus::Connection& conn, const string& config_name); + +unsigned int +command_create_xsnapshot(DBus::Connection& conn, const string& config_name, XSnapshotType type, + unsigned int prenum, const string& description, const string& cleanup, + const map& userdata); + diff --git a/client/snapper.cc b/client/snapper.cc new file mode 100644 index 00000000..a50e5cf4 --- /dev/null +++ b/client/snapper.cc @@ -0,0 +1,1271 @@ +/* + * Copyright (c) [2011-2012] Novell, Inc. + * + * 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 +#include +#include +#include +#include +#include + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tools/utils/Table.h" +#include "tools/utils/GetOpts.h" + +#include "commands.h" + +using namespace snapper; +using namespace std; + + +typedef void (*cmd_fnc)(DBus::Connection& conn); +map cmds; + +GetOpts getopts; + +bool quiet = false; +bool verbose = false; +string config_name = "root"; +bool disable_filters = false; + +Snapper* sh = NULL; + + +Snapshots::iterator +read_num(const string& str) +{ + Snapshots& snapshots = sh->getSnapshots(); + + istringstream s(str); + unsigned int num = 0; + s >> num; + + if (s.fail() || !s.eof()) + { + cerr << sformat(_("Invalid snapshot '%s'."), str.c_str()) << endl; + exit(EXIT_FAILURE); + } + + Snapshots::iterator snap = snapshots.find(num); + if (snap == snapshots.end()) + { + cerr << sformat(_("Snapshot '%u' not found."), num) << endl; + exit(EXIT_FAILURE); + } + + return snap; +} + + +pair +read_nums(const string& str) +{ + string::size_type pos = str.find(".."); + if (pos == string::npos) + { + cerr << _("Invalid snapshots.") << endl; + exit(EXIT_FAILURE); + } + + Snapshots::iterator snap1 = read_num(str.substr(0, pos)); + Snapshots::iterator snap2 = read_num(str.substr(pos + 2)); + + if (snap1 == snap2) + { + cerr << _("Identical snapshots.") << endl; + exit(EXIT_FAILURE); + } + + return pair(snap1, snap2); +} + + +map +read_userdata(const string& s, const map& old = map()) +{ + map userdata = old; + + list tmp; + boost::split(tmp, s, boost::is_any_of(","), boost::token_compress_on); + if (tmp.empty()) + { + cerr << _("Invalid userdata.") << endl; + exit(EXIT_FAILURE); + } + + for (list::const_iterator it = tmp.begin(); it != tmp.end(); ++it) + { + string::size_type pos = it->find("="); + if (pos == string::npos) + { + cerr << _("Invalid userdata.") << endl; + exit(EXIT_FAILURE); + } + + string key = boost::trim_copy(it->substr(0, pos)); + string value = boost::trim_copy(it->substr(pos + 1)); + + if (key.empty()) + { + cerr << _("Invalid userdata.") << endl; + exit(EXIT_FAILURE); + } + + if (value.empty()) + userdata.erase(key); + else + userdata[key] = value; + } + + return userdata; +} + + +string +show_userdata(const map& userdata) +{ + string s; + + for (map::const_iterator it = userdata.begin(); it != userdata.end(); ++it) + { + if (!s.empty()) + s += ", "; + s += it->first + "=" + it->second; + } + + return s; +} + + +void +help_list_configs() +{ + cout << _(" List configs:") << endl + << _("\tsnapper list-configs") << endl + << endl; +} + + +void +command_list_configs(DBus::Connection& conn) +{ + getopts.parse("list-configs", GetOpts::no_options); + if (getopts.hasArgs()) + { + cerr << _("Command 'list-configs' does not take arguments.") << endl; + exit(EXIT_FAILURE); + } + + Table table; + + TableHeader header; + header.add(_("Config")); + header.add(_("Subvolume")); + table.setHeader(header); + + try + { + list config_infos = command_list_xconfigs(conn); + for (list::const_iterator it = config_infos.begin(); it != config_infos.end(); ++it) + { + TableRow row; + row.add(it->config_name); + row.add(it->subvolume); + table.add(row); + } + } + catch (const ListConfigsFailedException& e) + { + cerr << sformat(_("Listing configs failed (%s)."), e.what()) << endl; + exit(EXIT_FAILURE); + } + + cout << table; +} + + +void +help_create_config() +{ + cout << _(" Create config:") << endl + << _("\tsnapper create-config ") << endl + << endl + << _(" Options for 'create-config' command:") << endl + << _("\t--fstype, -f \t\tManually set filesystem type.") << endl + << _("\t--template, -t \t\tName of config template to use.") << endl + << endl; +} + + +void +command_create_config() +{ + const struct option options[] = { + { "fstype", required_argument, 0, 'f' }, + { "template", required_argument, 0, 't' }, + { 0, 0, 0, 0 } + }; + + GetOpts::parsed_opts opts = getopts.parse("create-config", options); + if (getopts.numArgs() != 1) + { + cerr << _("Command 'create-config' needs one argument.") << endl; + exit(EXIT_FAILURE); + } + + string subvolume = realpath(getopts.popArg()); + if (subvolume.empty()) + { + cerr << _("Invalid subvolume.") << endl; + exit(EXIT_FAILURE); + } + + string fstype = ""; + string template_name = "default"; + + GetOpts::parsed_opts::const_iterator opt; + + if ((opt = opts.find("fstype")) != opts.end()) + fstype = opt->second; + + if ((opt = opts.find("template")) != opts.end()) + template_name = opt->second; + + if (fstype.empty() && !Snapper::detectFstype(subvolume, fstype)) + { + cerr << _("Detecting filesystem type failed.") << endl; + exit(EXIT_FAILURE); + } + + try + { + Snapper::createConfig(config_name, subvolume, fstype, template_name); + } + catch (const CreateConfigFailedException& e) + { + cerr << sformat(_("Creating config failed (%s)."), e.what()) << endl; + exit(EXIT_FAILURE); + } +} + + +void +help_delete_config() +{ + cout << _(" Delete config:") << endl + << _("\tsnapper delete-config") << endl + << endl; +} + + +void +command_delete_config() +{ + getopts.parse("delete-config", GetOpts::no_options); + if (getopts.hasArgs()) + { + cerr << _("Command 'delete-config' does not take arguments.") << endl; + exit(EXIT_FAILURE); + } + + try + { + Snapper::deleteConfig(config_name); + } + catch (const DeleteConfigFailedException& e) + { + cerr << sformat(_("Deleting config failed (%s)."), e.what()) << endl; + exit(EXIT_FAILURE); + } +} + + +void +help_list() +{ + cout << _(" List snapshots:") << endl + << _("\tsnapper list") << endl + << endl + << _(" Options for 'list' command:") << endl + << _("\t--type, -t \t\tType of snapshots to list.") << endl + << endl; +} + + +void +command_list() +{ + const struct option options[] = { + { "type", required_argument, 0, 't' }, + { 0, 0, 0, 0 } + }; + + GetOpts::parsed_opts opts = getopts.parse("list", options); + if (getopts.hasArgs()) + { + cerr << _("Command 'list' does not take arguments.") << endl; + exit(EXIT_FAILURE); + } + + enum ListMode { LM_ALL, LM_SINGLE, LM_PRE_POST }; + ListMode list_mode = LM_ALL; + + GetOpts::parsed_opts::const_iterator opt; + + if ((opt = opts.find("type")) != opts.end()) + { + if (opt->second == "all") + list_mode = LM_ALL; + else if (opt->second == "single") + list_mode = LM_SINGLE; + else if (opt->second == "pre-post") + list_mode = LM_PRE_POST; + else + { + cerr << _("Unknown type of snapshots.") << endl; + exit(EXIT_FAILURE); + } + } + + Table table; + + switch (list_mode) + { + case LM_ALL: + { + TableHeader header; + header.add(_("Type")); + header.add(_("#")); + header.add(_("Pre #")); + header.add(_("Date")); + header.add(_("Cleanup")); + header.add(_("Description")); + header.add(_("Userdata")); + table.setHeader(header); + + const Snapshots& snapshots = sh->getSnapshots(); + for (Snapshots::const_iterator it1 = snapshots.begin(); it1 != snapshots.end(); ++it1) + { + TableRow row; + row.add(toString(it1->getType())); + row.add(decString(it1->getNum())); + // row.add(it1->getType() == POST ? decString(it1->getPreNum()) : ""); + row.add(it1->isCurrent() ? "" : datetime(it1->getDate(), false, false)); + row.add(it1->getCleanup()); + row.add(it1->getDescription()); + row.add(show_userdata(it1->getUserdata())); + table.add(row); + } + } + break; + + case LM_SINGLE: + { + TableHeader header; + header.add(_("#")); + header.add(_("Date")); + header.add(_("Description")); + header.add(_("Userdata")); + table.setHeader(header); + + const Snapshots& snapshots = sh->getSnapshots(); + for (Snapshots::const_iterator it1 = snapshots.begin(); it1 != snapshots.end(); ++it1) + { + // if (it1->getType() != SINGLE) + // continue; + + TableRow row; + row.add(decString(it1->getNum())); + row.add(it1->isCurrent() ? "" : datetime(it1->getDate(), false, false)); + row.add(it1->getDescription()); + row.add(show_userdata(it1->getUserdata())); + table.add(row); + } + } + break; + + case LM_PRE_POST: + { + TableHeader header; + header.add(_("Pre #")); + header.add(_("Post #")); + header.add(_("Pre Date")); + header.add(_("Post Date")); + header.add(_("Description")); + header.add(_("Userdata")); + table.setHeader(header); + + const Snapshots& snapshots = sh->getSnapshots(); + for (Snapshots::const_iterator it1 = snapshots.begin(); it1 != snapshots.end(); ++it1) + { + // if (it1->getType() != PRE) + // continue; + + Snapshots::const_iterator it2 = snapshots.findPost(it1); + if (it2 == snapshots.end()) + continue; + + TableRow row; + row.add(decString(it1->getNum())); + row.add(decString(it2->getNum())); + row.add(datetime(it1->getDate(), false, false)); + row.add(datetime(it2->getDate(), false, false)); + row.add(it1->getDescription()); + row.add(show_userdata(it1->getUserdata())); + table.add(row); + } + } + break; + } + + cout << table; +} + + +void +help_create() +{ + cout << _(" Create snapshot:") << endl + << _("\tsnapper create") << endl + << endl + << _(" Options for 'create' command:") << endl + << _("\t--type, -t \t\tType for snapshot.") << endl + << _("\t--pre-number \t\tNumber of corresponding pre snapshot.") << endl + << _("\t--print-number, -p\t\tPrint number of created snapshot.") << endl + << _("\t--description, -d \tDescription for snapshot.") << endl + << _("\t--cleanup-algorithm, -c \tCleanup algorithm for snapshot.") << endl + << _("\t--userdata, -u \tUserdata for snapshot.") << endl + << _("\t--command \tRun command and create pre and post snapshots.") << endl + << endl; +} + + +void +command_create() +{ + const struct option options[] = { + { "type", required_argument, 0, 't' }, + { "pre-number", required_argument, 0, 0 }, + { "print-number", no_argument, 0, 'p' }, + { "description", required_argument, 0, 'd' }, + { "cleanup-algorithm", required_argument, 0, 'c' }, + { "userdata", required_argument, 0, 'u' }, + { "command", required_argument, 0, 0 }, + { 0, 0, 0, 0 } + }; + + GetOpts::parsed_opts opts = getopts.parse("create", options); + if (getopts.hasArgs()) + { + cerr << _("Command 'create' does not take arguments.") << endl; + exit(EXIT_FAILURE); + } + + enum CreateType { CT_SINGLE, CT_PRE, CT_POST, CT_PRE_POST }; + + const Snapshots& snapshots = sh->getSnapshots(); + + CreateType type = CT_SINGLE; + Snapshots::const_iterator snap1 = snapshots.end(); + bool print_number = false; + string description; + string cleanup; + map userdata; + string command; + + GetOpts::parsed_opts::const_iterator opt; + + if ((opt = opts.find("type")) != opts.end()) + { + if (opt->second == "single") + type = CT_SINGLE; + else if (opt->second == "pre") + type = CT_PRE; + else if (opt->second == "post") + type = CT_POST; + else if (opt->second == "pre-post") + type = CT_PRE_POST; + else + { + cerr << _("Unknown type of snapshot.") << endl; + exit(EXIT_FAILURE); + } + } + + if ((opt = opts.find("pre-number")) != opts.end()) + snap1 = read_num(opt->second); + + if ((opt = opts.find("print-number")) != opts.end()) + print_number = true; + + if ((opt = opts.find("description")) != opts.end()) + description = opt->second; + + if ((opt = opts.find("cleanup-algorithm")) != opts.end()) + cleanup = opt->second; + + if ((opt = opts.find("userdata")) != opts.end()) + userdata = read_userdata(opt->second); + + if ((opt = opts.find("command")) != opts.end()) + { + command = opt->second; + type = CT_PRE_POST; + } + + if (type == CT_POST && (snap1 == snapshots.end() || snap1->isCurrent())) + { + cerr << _("Missing or invalid pre-number.") << endl; + exit(EXIT_FAILURE); + } + + if (type == CT_PRE_POST && command.empty()) + { + cerr << _("Missing command argument.") << endl; + exit(EXIT_FAILURE); + } + + try + { + switch (type) + { + case CT_SINGLE: { + Snapshots::iterator snap1 = sh->createSingleSnapshot(description); + snap1->setCleanup(cleanup); + snap1->setUserdata(userdata); + snap1->flushInfo(); + if (print_number) + cout << snap1->getNum() << endl; + } break; + + case CT_PRE: { + Snapshots::iterator snap1 = sh->createPreSnapshot(description); + snap1->setCleanup(cleanup); + snap1->setUserdata(userdata); + snap1->flushInfo(); + if (print_number) + cout << snap1->getNum() << endl; + } break; + + case CT_POST: { + Snapshots::iterator snap2 = sh->createPostSnapshot(description, snap1); + snap2->setCleanup(cleanup); + snap2->setUserdata(userdata); + snap2->flushInfo(); + if (print_number) + cout << snap2->getNum() << endl; + sh->startBackgroundComparsion(snap1, snap2); + } break; + + case CT_PRE_POST: { + Snapshots::iterator snap1 = sh->createPreSnapshot(description); + snap1->setCleanup(cleanup); + snap1->setUserdata(userdata); + snap1->flushInfo(); + + system(command.c_str()); + + Snapshots::iterator snap2 = sh->createPostSnapshot("", snap1); + snap2->setCleanup(cleanup); + snap2->setUserdata(userdata); + snap2->flushInfo(); + if (print_number) + cout << snap1->getNum() << ".." << snap2->getNum() << endl; + sh->startBackgroundComparsion(snap1, snap2); + } break; + } + } + catch (const InvalidUserdataException& e) + { + cerr << _("Invalid userdata.") << endl; + exit(EXIT_FAILURE); + } +} + + +void +help_modify() +{ + cout << _(" Modify snapshot:") << endl + << _("\tsnapper modify ") << endl + << endl + << _(" Options for 'modify' command:") << endl + << _("\t--description, -d \tDescription for snapshot.") << endl + << _("\t--cleanup-algorithm, -c \tCleanup algorithm for snapshot.") << endl + << _("\t--userdata, -u \tUserdata for snapshot.") << endl + << endl; +} + + +void +command_modify() +{ + const struct option options[] = { + { "description", required_argument, 0, 'd' }, + { "cleanup-algorithm", required_argument, 0, 'c' }, + { "userdata", required_argument, 0, 'u' }, + { 0, 0, 0, 0 } + }; + + GetOpts::parsed_opts opts = getopts.parse("modify", options); + + if (!getopts.hasArgs()) + { + cerr << _("Command 'modify' needs at least one argument.") << endl; + exit(EXIT_FAILURE); + } + + try + { + while (getopts.hasArgs()) + { + Snapshots::iterator snapshot = read_num(getopts.popArg()); + + GetOpts::parsed_opts::const_iterator opt; + + if ((opt = opts.find("description")) != opts.end()) + snapshot->setDescription(opt->second); + + if ((opt = opts.find("cleanup-algorithm")) != opts.end()) + snapshot->setCleanup(opt->second); + + if ((opt = opts.find("userdata")) != opts.end()) + snapshot->setUserdata(read_userdata(opt->second, snapshot->getUserdata())); + + snapshot->flushInfo(); + } + } + catch (const IllegalSnapshotException& e) + { + cerr << _("Invalid snapshot.") << endl; + exit(EXIT_FAILURE); + } + catch (const InvalidUserdataException& e) + { + cerr << _("Invalid userdata.") << endl; + exit(EXIT_FAILURE); + } +} + + +void +help_delete() +{ + cout << _(" Delete snapshot:") << endl + << _("\tsnapper delete ") << endl + << endl; +} + + +void +command_delete() +{ + getopts.parse("delete", GetOpts::no_options); + if (!getopts.hasArgs()) + { + cerr << _("Command 'delete' needs at least one argument.") << endl; + exit(EXIT_FAILURE); + } + + try + { + while (getopts.hasArgs()) + { + Snapshots::iterator snapshot = read_num(getopts.popArg()); + + sh->deleteSnapshot(snapshot); + } + } + catch (const IllegalSnapshotException& e) + { + cerr << _("Invalid snapshot.") << endl; + exit(EXIT_FAILURE); + } +} + + +void +help_mount() +{ + cout << _(" Mount snapshot:") << endl + << _("\tsnapper mount ") << endl + << endl; +} + + +void +command_mount() +{ + getopts.parse("mount", GetOpts::no_options); + if (!getopts.hasArgs()) + { + cerr << _("Command 'mount' needs at least one argument.") << endl; + exit(EXIT_FAILURE); + } + + try + { + while (getopts.hasArgs()) + { + Snapshots::iterator snapshot = read_num(getopts.popArg()); + + snapshot->mountFilesystemSnapshot(); + } + } + catch (const IllegalSnapshotException& e) + { + cerr << _("Invalid snapshot.") << endl; + exit(EXIT_FAILURE); + } +} + + +void +help_umount() +{ + cout << _(" Umount snapshot:") << endl + << _("\tsnapper umount ") << endl + << endl; +} + + +void +command_umount() +{ + getopts.parse("mount", GetOpts::no_options); + if (!getopts.hasArgs()) + { + cerr << _("Command 'mount' needs at least one argument.") << endl; + exit(EXIT_FAILURE); + } + + try + { + while (getopts.hasArgs()) + { + Snapshots::iterator snapshot = read_num(getopts.popArg()); + + snapshot->umountFilesystemSnapshot(); + } + } + catch (const IllegalSnapshotException& e) + { + cerr << _("Invalid snapshot.") << endl; + exit(EXIT_FAILURE); + } +} + + +void +help_status() +{ + cout << _(" Comparing snapshots:") << endl + << _("\tsnapper status ..") << endl + << endl + << _(" Options for 'status' command:") << endl + << _("\t--output, -o \t\tSave status to file.") << endl + << endl; +} + + +void +command_status() +{ + const struct option options[] = { + { "output", required_argument, 0, 'o' }, + { 0, 0, 0, 0 } + }; + + GetOpts::parsed_opts opts = getopts.parse("status", options); + if (getopts.numArgs() != 1) + { + cerr << _("Command 'status' needs one argument.") << endl; + exit(EXIT_FAILURE); + } + + GetOpts::parsed_opts::const_iterator opt; + + pair snaps(read_nums(getopts.popArg())); + + Comparison comparison(sh, snaps.first, snaps.second); + + const Files& files = comparison.getFiles(); + + FILE* file = stdout; + + if ((opt = opts.find("output")) != opts.end()) + { + file = fopen(opt->second.c_str(), "w"); + if (!file) + { + cerr << sformat(_("Opening file '%s' failed."), opt->second.c_str()) << endl; + exit(EXIT_FAILURE); + } + } + + for (Files::const_iterator it = files.begin(); it != files.end(); ++it) + fprintf(file, "%s %s\n", statusToString(it->getPreToPostStatus()).c_str(), + it->getAbsolutePath(LOC_SYSTEM).c_str()); + + if (file != stdout) + fclose(file); +} + + +void +help_diff() +{ + cout << _(" Comparing snapshots:") << endl + << _("\tsnapper diff .. [files]") << endl + << endl; +} + + +void +command_diff() +{ + GetOpts::parsed_opts opts = getopts.parse("diff", GetOpts::no_options); + + GetOpts::parsed_opts::const_iterator opt; + + pair snaps(read_nums(getopts.popArg())); + + Comparison comparison(sh, snaps.first, snaps.second); + + const Files& files = comparison.getFiles(); + + if (getopts.numArgs() == 0) + { + for (Files::const_iterator it1 = files.begin(); it1 != files.end(); ++it1) + { + vector lines = it1->getDiff("--unified --new-file"); + for (vector::const_iterator it2 = lines.begin(); it2 != lines.end(); ++it2) + cout << it2->c_str() << endl; + } + } + else + { + while (getopts.numArgs() > 0) + { + string name = getopts.popArg(); + + Files::const_iterator tmp = files.findAbsolutePath(name); + if (tmp == files.end()) + continue; + + vector lines = tmp->getDiff("--unified --new-file"); + for (vector::const_iterator it2 = lines.begin(); it2 != lines.end(); ++it2) + cout << it2->c_str() << endl; + } + } +} + + +void +help_undo() +{ + cout << _(" Undo changes:") << endl + << _("\tsnapper undochange .. [files]") << endl + << endl + << _(" Options for 'undochange' command:") << endl + << _("\t--input, -i \t\tRead files for which to undo changes from file.") << endl + << endl; +} + + +void +command_undo() +{ + const struct option options[] = { + { "input", required_argument, 0, 'i' }, + { 0, 0, 0, 0 } + }; + + GetOpts::parsed_opts opts = getopts.parse("undochange", options); + if (getopts.numArgs() < 1) + { + cerr << _("Command 'undochange' needs at least one argument.") << endl; + exit(EXIT_FAILURE); + } + + pair snaps(read_nums(getopts.popArg())); + + FILE* file = NULL; + + GetOpts::parsed_opts::const_iterator opt; + + if ((opt = opts.find("input")) != opts.end()) + { + file = fopen(opt->second.c_str(), "r"); + if (!file) + { + cerr << sformat(_("Opening file '%s' failed."), opt->second.c_str()) << endl; + exit(EXIT_FAILURE); + } + } + + if (snaps.first->isCurrent()) + { + cerr << _("Invalid snapshots.") << endl; + exit(EXIT_FAILURE); + } + + Comparison comparison(sh, snaps.first, snaps.second); + + Files& files = comparison.getFiles(); + + if (file) + { + AsciiFileReader asciifile(file); + + string line; + while (asciifile.getline(line)) + { + if (line.empty()) + continue; + + string name = line; + + // strip optional status + if (name[0] != '/') + { + string::size_type pos = name.find(" "); + if (pos == string::npos) + continue; + + name.erase(0, pos + 1); + } + + Files::iterator it = files.findAbsolutePath(name); + if (it == files.end()) + { + cerr << sformat(_("File '%s' not found."), name.c_str()) << endl; + exit(EXIT_FAILURE); + } + + it->setUndo(true); + } + } + else + { + if (getopts.numArgs() == 0) + { + for (Files::iterator it = files.begin(); it != files.end(); ++it) + it->setUndo(true); + } + else + { + while (getopts.numArgs() > 0) + { + string name = getopts.popArg(); + + Files::iterator tmp = files.findAbsolutePath(name); + if (tmp == files.end()) + continue; + + tmp->setUndo(true); + } + } + } + + UndoStatistic rs = comparison.getUndoStatistic(); + + if (rs.empty()) + { + cout << "nothing to do" << endl; + return; + } + + cout << "create:" << rs.numCreate << " modify:" << rs.numModify << " delete:" << rs.numDelete + << endl; + + comparison.doUndo(); +} + + +void +help_cleanup() +{ + cout << _(" Cleanup snapshots:") << endl + << _("\tsnapper cleanup ") << endl + << endl; +} + + +void +command_cleanup() +{ + const struct option options[] = { + { 0, 0, 0, 0 } + }; + + GetOpts::parsed_opts opts = getopts.parse("cleanup", options); + if (getopts.numArgs() != 1) + { + cerr << _("Command 'cleanup' needs one arguments.") << endl; + exit(EXIT_FAILURE); + } + + string cleanup = getopts.popArg(); + + if (cleanup == "number") + { + sh->doCleanupNumber(); + } + else if (cleanup == "timeline") + { + sh->doCleanupTimeline(); + } + else if (cleanup == "empty-pre-post") + { + sh->doCleanupEmptyPrePost(); + } + else + { + cerr << sformat(_("Unknown cleanup algorithm '%s'."), cleanup.c_str()) << endl; + exit(EXIT_FAILURE); + } +} + + +void +command_help() +{ + getopts.parse("help", GetOpts::no_options); + if (getopts.hasArgs()) + { + cerr << _("Command 'help' does not take arguments.") << endl; + exit(EXIT_FAILURE); + } + + cout << _("usage: snapper [--global-options] [--command-options] [command-arguments]") << endl + << endl; + + cout << _(" Global options:") << endl + << _("\t--quiet, -q\t\t\tSuppress normal output.") << endl + << _("\t--verbose, -v\t\t\tIncrease verbosity.") << endl + << _("\t--table-style, -t