]> git.ipfire.org Git - thirdparty/snapper.git/commitdiff
- started work on DBus interface
authorArvin Schnell <aschnell@suse.de>
Thu, 26 Apr 2012 13:48:42 +0000 (15:48 +0200)
committerArvin Schnell <aschnell@suse.de>
Thu, 26 Apr 2012 13:48:42 +0000 (15:48 +0200)
42 files changed:
Makefile.am
client/.gitignore [new file with mode: 0644]
client/Makefile.am [new file with mode: 0644]
client/commands.cc [new file with mode: 0644]
client/commands.h [new file with mode: 0644]
client/snapper.cc [new file with mode: 0644]
client/types.cc [new file with mode: 0644]
client/types.h [new file with mode: 0644]
configure.in
data/Makefile.am
data/org.opensuse.snapper.conf [new file with mode: 0644]
data/org.opensuse.snapper.service [new file with mode: 0644]
dbus/.gitignore [new file with mode: 0644]
dbus/DBusConnection.cc [new file with mode: 0644]
dbus/DBusConnection.h [new file with mode: 0644]
dbus/DBusMessage.cc [new file with mode: 0644]
dbus/DBusMessage.h [new file with mode: 0644]
dbus/Makefile.am [new file with mode: 0644]
examples/python/comparison.py [new file with mode: 0755]
examples/python/create.py [new file with mode: 0755]
examples/python/debug.py [new file with mode: 0755]
examples/python/delete.py [new file with mode: 0755]
examples/python/list-configs.py [new file with mode: 0755]
examples/python/list.py [new file with mode: 0755]
examples/python/lock-config.py [new file with mode: 0755]
examples/qt/client-qt.cc [new file with mode: 0644]
server/.gitignore [new file with mode: 0644]
server/Client.cc [new file with mode: 0644]
server/Client.h [new file with mode: 0644]
server/Job.cc [new file with mode: 0644]
server/Job.h [new file with mode: 0644]
server/Makefile.am [new file with mode: 0644]
server/MetaSnapper.cc [new file with mode: 0644]
server/MetaSnapper.h [new file with mode: 0644]
server/Types.cc [new file with mode: 0644]
server/Types.h [new file with mode: 0644]
server/snapperd.cc [new file with mode: 0644]
snapper/Comparison.cc
snapper/Comparison.h
snapper/Makefile.am
snapper/Snapper.cc
snapper/Snapper.h

index e4080fa750ee73efe9de2fd9e668be31688d80d1..a6cae224de9f9b2d20c652ecd279d2ab10d5554f 100644 (file)
@@ -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 (file)
index 0000000..75c800d
--- /dev/null
@@ -0,0 +1,2 @@
+*.o
+snapper
diff --git a/client/Makefile.am b/client/Makefile.am
new file mode 100644 (file)
index 0000000..7c04959
--- /dev/null
@@ -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 (file)
index 0000000..e1a688f
--- /dev/null
@@ -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 <unistd.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dbus/dbus.h>
+
+#include "commands.h"
+
+
+list<XConfigInfo>
+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<XConfigInfo> ret;
+
+    DBus::Hihi hihi(reply);
+    hihi >> ret;
+
+    return ret;
+}
+
+
+list<XSnapshot>
+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<XSnapshot> 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<string, string>& 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 (file)
index 0000000..dbb16cc
--- /dev/null
@@ -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 <unistd.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dbus/dbus.h>
+
+#include <string>
+#include <list>
+#include <map>
+
+using std::string;
+using std::list;
+using std::map;
+
+#include "types.h"
+
+
+list<XConfigInfo>
+command_list_xconfigs(DBus::Connection& conn);
+
+list<XSnapshot>
+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<string, string>& userdata);
+
diff --git a/client/snapper.cc b/client/snapper.cc
new file mode 100644 (file)
index 0000000..a50e5cf
--- /dev/null
@@ -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 <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <iostream>
+#include <boost/algorithm/string.hpp>
+
+#include "config.h"
+#include <snapper/Factory.h>
+#include <snapper/Snapper.h>
+#include <snapper/Snapshot.h>
+#include <snapper/Comparison.h>
+#include <snapper/File.h>
+#include <snapper/AppUtil.h>
+#include <snapper/SnapperTmpl.h>
+#include <snapper/Compare.h>
+#include <snapper/Enum.h>
+#include <snapper/AsciiFile.h>
+
+#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<string, cmd_fnc> 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<Snapshots::iterator, Snapshots::iterator>
+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<Snapshots::iterator, Snapshots::iterator>(snap1, snap2);
+}
+
+
+map<string, string>
+read_userdata(const string& s, const map<string, string>& old = map<string, string>())
+{
+    map<string, string> userdata = old;
+
+    list<string> 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<string>::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<string, string>& userdata)
+{
+    string s;
+
+    for (map<string, string>::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<XConfigInfo> config_infos = command_list_xconfigs(conn);
+       for (list<XConfigInfo>::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 <subvolume>") << endl
+        << endl
+        << _("    Options for 'create-config' command:") << endl
+        << _("\t--fstype, -f <fstype>\t\tManually set filesystem type.") << endl
+        << _("\t--template, -t <name>\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 <type>\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 <type>\t\tType for snapshot.") << endl
+        << _("\t--pre-number <number>\t\tNumber of corresponding pre snapshot.") << endl
+        << _("\t--print-number, -p\t\tPrint number of created snapshot.") << endl
+        << _("\t--description, -d <description>\tDescription for snapshot.") << endl
+        << _("\t--cleanup-algorithm, -c <algo>\tCleanup algorithm for snapshot.") << endl
+        << _("\t--userdata, -u <userdata>\tUserdata for snapshot.") << endl
+        << _("\t--command <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<string, string> 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 <number>") << endl
+        << endl
+        << _("    Options for 'modify' command:") << endl
+        << _("\t--description, -d <description>\tDescription for snapshot.") << endl
+        << _("\t--cleanup-algorithm, -c <algo>\tCleanup algorithm for snapshot.") << endl
+        << _("\t--userdata, -u <userdata>\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 <number>") << 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 <number>") << 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 <number>") << 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 <number1>..<number2>") << endl
+        << endl
+        << _("    Options for 'status' command:") << endl
+        << _("\t--output, -o <file>\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<Snapshots::const_iterator, Snapshots::const_iterator> 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 <number1>..<number2> [files]") << endl
+        << endl;
+}
+
+
+void
+command_diff()
+{
+    GetOpts::parsed_opts opts = getopts.parse("diff", GetOpts::no_options);
+
+    GetOpts::parsed_opts::const_iterator opt;
+
+    pair<Snapshots::const_iterator, Snapshots::const_iterator> 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<string> lines = it1->getDiff("--unified --new-file");
+           for (vector<string>::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<string> lines = tmp->getDiff("--unified --new-file");
+           for (vector<string>::const_iterator it2 = lines.begin(); it2 != lines.end(); ++it2)
+               cout << it2->c_str() << endl;
+       }
+    }
+}
+
+
+void
+help_undo()
+{
+    cout << _("  Undo changes:") << endl
+        << _("\tsnapper undochange <number1>..<number2> [files]") << endl
+        << endl
+        << _("    Options for 'undochange' command:") << endl
+        << _("\t--input, -i <file>\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<Snapshots::const_iterator, Snapshots::const_iterator> 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 <cleanup-algorithm>") << 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> [--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 <style>\tTable style (integer).") << endl
+        << _("\t--config, -c <name>\t\tSet name of config to use.") << endl
+        << _("\t--disable-filters\t\tDisable filters.") << endl
+        << _("\t--version\t\t\tPrint version and exit.") << endl
+        << endl;
+
+    help_list_configs();
+    help_create_config();
+    help_delete_config();
+    help_list();
+    help_create();
+    help_modify();
+    help_delete();
+    help_mount();
+    help_umount();
+    help_status();
+    help_diff();
+    help_undo();
+    help_cleanup();
+}
+
+
+struct CompareCallbackImpl : public CompareCallback
+{
+    void start() { cout << _("comparing snapshots...") << flush; }
+    void stop() { cout << " " << _("done") << endl; }
+};
+
+CompareCallbackImpl compare_callback_impl;
+
+
+struct UndoCallbackImpl : public UndoCallback
+{
+    void start() { cout << _("undoing change...") << endl; }
+    void stop() { cout << _("undoing change done") << endl; }
+
+    void createInfo(const string& name)
+       { if (verbose) cout << sformat(_("creating %s"), name.c_str()) << endl; }
+    void modifyInfo(const string& name)
+       { if (verbose) cout << sformat(_("modifying %s"), name.c_str()) << endl; }
+    void deleteInfo(const string& name)
+       { if (verbose) cout << sformat(_("deleting %s"), name.c_str()) << endl; }
+
+    void createError(const string& name)
+       { cerr << sformat(_("failed to create %s"), name.c_str()) << endl; }
+    void modifyError(const string& name)
+       { cerr << sformat(_("failed to modify %s"), name.c_str()) << endl; }
+    void deleteError(const string& name)
+       { cerr << sformat(_("failed to delete %s"), name.c_str()) << endl; }
+};
+
+UndoCallbackImpl undo_callback_impl;
+
+
+int
+main(int argc, char** argv)
+{
+    umask(0027);
+
+    setlocale(LC_ALL, "");
+
+    initDefaultLogger();
+
+    cmds["list-configs"] = command_list_configs;
+    // cmds["create-config"] = command_create_config;
+    // cmds["delete-config"] = command_delete_config;
+    // cmds["list"] = command_list;
+    // cmds["create"] = command_create;
+    // cmds["modify"] = command_modify;
+    // cmds["delete"] = command_delete;
+    // cmds["mount"] = command_mount;
+    // cmds["umount"] = command_umount;
+    // cmds["status"] = command_status;
+    // cmds["diff"] = command_diff;
+    // cmds["undochange"] = command_undo;
+    // cmds["cleanup"] = command_cleanup;
+    // cmds["help"] = command_help;
+
+    const struct option options[] = {
+       { "quiet",              no_argument,            0,      'q' },
+       { "verbose",            no_argument,            0,      'v' },
+       { "table-style",        required_argument,      0,      't' },
+       { "config",             required_argument,      0,      'c' },
+       { "disable-filters",    no_argument,            0,      0 },
+       { "version",            no_argument,            0,      0 },
+       { "help",               no_argument,            0,      0 },
+       { 0, 0, 0, 0 }
+    };
+
+    getopts.init(argc, argv);
+
+    GetOpts::parsed_opts opts = getopts.parse(options);
+
+    GetOpts::parsed_opts::const_iterator opt;
+
+    if ((opt = opts.find("quiet")) != opts.end())
+       quiet = true;
+
+    if ((opt = opts.find("verbose")) != opts.end())
+       verbose = true;
+
+    if ((opt = opts.find("table-style")) != opts.end())
+    {
+       unsigned int s;
+       opt->second >> s;
+       if (s >= _End)
+       {
+           cerr << sformat(_("Invalid table style %d."), s) << " "
+                << sformat(_("Use an integer number from %d to %d"), 0, _End - 1) << endl;
+           exit(EXIT_FAILURE);
+       }
+       Table::defaultStyle = (TableLineStyle) s;
+    }
+
+    if ((opt = opts.find("config")) != opts.end())
+       config_name = opt->second;
+
+    if ((opt = opts.find("disable-filters")) != opts.end())
+       disable_filters = true;
+
+    if ((opt = opts.find("version")) != opts.end())
+    {
+       cout << "snapper " << VERSION << endl;
+       exit(EXIT_SUCCESS);
+    }
+
+    if ((opt = opts.find("help")) != opts.end())
+    {
+       command_help();
+       exit(EXIT_SUCCESS);
+    }
+
+    if (!getopts.hasArgs())
+    {
+       cerr << _("No command provided.") << endl
+            << _("Try 'snapper help' for more information.") << endl;
+       exit(EXIT_FAILURE);
+    }
+
+    const char* command = getopts.popArg();
+    map<string, cmd_fnc>::const_iterator cmd = cmds.find(command);
+    if (cmd == cmds.end())
+    {
+       cerr << sformat(_("Unknown command '%s'."), command) << endl
+            << _("Try 'snapper help' for more information.") << endl;
+       exit(EXIT_FAILURE);
+    }
+
+    DBus::Connection conn(DBUS_BUS_SYSTEM);
+
+    if (cmd->first == "help" || cmd->first == "list-configs" ||
+       cmd->first == "create-config" || cmd->first == "delete-config")
+    {
+       (*cmd->second)(conn);
+    }
+    else
+    {
+       try
+       {
+           sh = createSnapper(config_name, disable_filters);
+       }
+       catch (const ConfigNotFoundException& e)
+       {
+           cerr << sformat(_("Config '%s' not found."), config_name.c_str()) << endl;
+           exit(EXIT_FAILURE);
+       }
+       catch (const InvalidConfigException& e)
+       {
+           cerr << sformat(_("Config '%s' is invalid."), config_name.c_str()) << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       if (!quiet)
+       {
+           sh->setCompareCallback(&compare_callback_impl);
+           sh->setUndoCallback(&undo_callback_impl);
+       }
+
+       try
+       {
+           (*cmd->second)(conn);
+       }
+       catch (const SnapperException& e)
+       {
+           y2err("caught final exception");
+           cerr << sformat(_("Command failed (%s). See log for more information."),
+                           e.what()) << endl;
+           exit(EXIT_FAILURE);
+       }
+
+       deleteSnapper(sh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/client/types.cc b/client/types.cc
new file mode 100644 (file)
index 0000000..66f3440
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * 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 <unistd.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dbus/dbus.h>
+
+#include "types.h"
+
+
+namespace DBus
+{
+    const char* TypeInfo<XConfigInfo>::signature = "(ss)";
+    const char* TypeInfo<XSnapshot>::signature = "(uqusa{ss})";
+    const char* TypeInfo<XFile>::signature = "(ssb)";
+    const char* TypeInfo<XUndo>::signature = "(sb)";
+    
+    
+    Hihi&
+    operator>>(Hihi& hihi, XConfigInfo& data)
+    {
+       hihi.open_recurse();
+       hihi >> data.config_name >> data.subvolume;
+       hihi.close_recurse();
+       return hihi;
+    }
+    
+    
+    Hihi&
+    operator>>(Hihi& hihi, XSnapshotType& data)
+    {
+       dbus_uint16_t tmp;
+       hihi >> tmp;
+       data = static_cast<XSnapshotType>(tmp);
+       return hihi;
+    }
+    
+    
+    Hihi&
+    operator>>(Hihi& hihi, XSnapshot& data)
+    {
+       hihi.open_recurse();
+       hihi >> data.num >> data.type >> data.date >> data.description >> data.userdata;
+       hihi.close_recurse();
+       return hihi;
+    }
+    
+    
+    Hihi&
+    operator>>(Hihi& hihi, XFile& data)
+    {
+       hihi.open_recurse();
+       hihi >> data.filename >> data.status >> data.undo;
+       hihi.close_recurse();
+       return hihi;
+    }
+    
+    
+    Hoho&
+    operator<<(Hoho& hoho, XSnapshotType data)
+    {
+       hoho << static_cast<dbus_uint16_t>(data);
+       return hoho;
+    }
+    
+
+    Hoho&
+    operator<<(Hoho& hoho, const XUndo& data)
+    {
+       hoho.open_struct();
+       hoho << data.filename << data.undo;
+       hoho.close_struct();
+       return hoho;
+    }
+    
+}
+
diff --git a/client/types.h b/client/types.h
new file mode 100644 (file)
index 0000000..92f2497
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * 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 <unistd.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dbus/dbus.h>
+
+#include <string>
+#include <list>
+#include <map>
+
+using std::string;
+using std::list;
+using std::map;
+
+#include "dbus/DBusConnection.h"
+#include "dbus/DBusMessage.h"
+
+
+struct XConfigInfo
+{
+    string config_name;
+    string subvolume;
+};
+
+
+enum XSnapshotType { SINGLE, PRE, POST };
+
+
+struct XSnapshot
+{
+    unsigned int num;
+    XSnapshotType type;
+    unsigned int date;
+    string description;
+    map<string, string> userdata;
+};
+
+
+struct XFile
+{
+    string status;
+    string filename;
+    bool undo;
+};
+
+
+struct XUndo
+{
+    string filename;
+    bool undo;
+};
+
+
+namespace DBus
+{
+    
+    template <> struct TypeInfo<XSnapshot> { static const char* signature; };
+    template <> struct TypeInfo<XConfigInfo> { static const char* signature; };
+    template <> struct TypeInfo<XFile> { static const char* signature; };
+    template <> struct TypeInfo<XUndo> { static const char* signature; };
+   
+    Hihi& operator>>(Hihi& hihi, XConfigInfo& data);
+    Hihi& operator>>(Hihi& hihi, XSnapshotType& data);
+    Hihi& operator>>(Hihi& hihi, XSnapshot& data);
+    Hihi& operator>>(Hihi& hihi, XFile& data);
+
+    Hoho& operator<<(Hoho& hoho, XSnapshotType data);
+    Hoho& operator<<(Hoho& hoho, const XUndo& data);
+
+};
+
index 44c4d77268c14f50abde9ac37e63fe8a8e24e2a2..c55a0bbeaf5b41504f2026b8dc7e5411b23849a5 100644 (file)
@@ -55,6 +55,9 @@ AC_OUTPUT(
        bindings/python/Makefile
        tools/Makefile
        tools/utils/Makefile
+       dbus/Makefile
+       server/Makefile
+       client/Makefile
        scripts/Makefile
        data/Makefile
        doc/Makefile
index 90c5b7b19e86f9bbcdb0c5268db827098260c4fc..53281f38370706c89114713a017e1d8987c93f13 100644 (file)
@@ -2,7 +2,8 @@
 # Makefile.am for snapper/data
 #
 
-EXTRA_DIST = sysconfig.snapper base.txt x11.txt snapper.logrotate default-config
+EXTRA_DIST = sysconfig.snapper base.txt x11.txt snapper.logrotate default-config       \
+       org.opensuse.snapper.conf org.opensuse.snapper.service
 
 install-data-local:
        install -D -m 644 snapper.logrotate $(DESTDIR)/etc/logrotate.d/snapper
@@ -11,4 +12,5 @@ install-data-local:
        install -d -m 755 $(DESTDIR)/etc/snapper/filters
        install -D -m 644 base.txt $(DESTDIR)/etc/snapper/filters/base.txt
        install -D -m 644 x11.txt $(DESTDIR)/etc/snapper/filters/x11.txt
-
+       install -D -m 644 org.opensuse.snapper.conf /etc/dbus-1/system.d/org.opensuse.snapper.conf
+       install -D -m 644 org.opensuse.snapper.service /usr/share/dbus-1/system-services/org.opensuse.snapper.service
diff --git a/data/org.opensuse.snapper.conf b/data/org.opensuse.snapper.conf
new file mode 100644 (file)
index 0000000..28242ff
--- /dev/null
@@ -0,0 +1,15 @@
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+
+  <policy user="root">
+    <allow own="org.opensuse.snapper"/>
+    <allow send_destination="org.opensuse.snapper" send_interface="org.opensuse.snapper"/>
+    <allow send_destination="org.opensuse.snapper" send_interface="org.freedesktop.DBus.Introspectable"/>
+  </policy>
+  <policy context="default">
+    <deny own="org.opensuse.snapper"/>
+    <allow send_destination="org.opensuse.snapper" send_interface="org.opensuse.snapper"/>
+    <allow send_destination="org.opensuse.snapper" send_interface="org.freedesktop.DBus.Introspectable"/>
+  </policy>
+
+</busconfig>
diff --git a/data/org.opensuse.snapper.service b/data/org.opensuse.snapper.service
new file mode 100644 (file)
index 0000000..63f35f2
--- /dev/null
@@ -0,0 +1,5 @@
+# DBus service activation config
+[D-BUS Service]
+Name=org.opensuse.snapper
+Exec=/usr/sbin/snapperd
+User=root
diff --git a/dbus/.gitignore b/dbus/.gitignore
new file mode 100644 (file)
index 0000000..66a3f3f
--- /dev/null
@@ -0,0 +1,2 @@
+*.lo
+*.la
diff --git a/dbus/DBusConnection.cc b/dbus/DBusConnection.cc
new file mode 100644 (file)
index 0000000..ed860fe
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * 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 <stdlib.h>
+#include <sys/types.h>
+#include <pwd.h>
+
+#include "DBusConnection.h"
+
+namespace DBus
+{
+
+    Connection::Connection(DBusBusType type)
+    {
+       DBusError err;
+       dbus_error_init(&err);
+
+       conn = dbus_bus_get(type, &err);
+       if (dbus_error_is_set(&err))
+       {
+           dbus_error_free(&err);
+           throw FatalException();
+       }
+
+       if (!conn)
+       {
+           throw FatalException();
+       }
+    }
+
+
+    Connection::~Connection()
+    {
+       dbus_connection_unref(conn);
+    }
+
+
+    void
+    Connection::request_name(const char* name, unsigned int flags)
+    {
+       DBusError err;
+       dbus_error_init(&err);
+
+       int ret = dbus_bus_request_name(conn, name, flags, &err);
+       if (dbus_error_is_set(&err))
+       {
+           dbus_error_free(&err);
+           throw FatalException();
+       }
+
+       if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
+       {
+           throw FatalException();
+       }
+    }
+
+
+    void
+    Connection::read_write(int timeout)
+    {
+       dbus_connection_read_write(conn, timeout);
+    }
+
+
+    void
+    Connection::send(Message& m)
+    {
+       if (!dbus_connection_send(conn, m.get_message(), NULL))
+       {
+           throw FatalException();
+       }
+    }
+
+
+    Message
+    Connection::send_and_reply_and_block(Message& m)
+    {
+       DBusError err;
+       dbus_error_init(&err);
+
+       DBusMessage* tmp = dbus_connection_send_with_reply_and_block(conn, m.get_message(),
+                                                                    0x7fffffff, &err);
+       if (dbus_error_is_set(&err))
+       {
+           dbus_error_free(&err);
+           throw FatalException();
+       }
+
+       Message reply(tmp);
+       return reply;
+    }
+
+
+    void
+    Connection::add_match(const char* rule)
+    {
+       DBusError err;
+       dbus_error_init(&err);
+
+       dbus_bus_add_match(conn, rule, &err);
+
+       if (dbus_error_is_set(&err))
+       {
+           throw FatalException();
+       }
+    }
+
+
+    unsigned long
+    Connection::get_unix_userid(const Message& m)
+    {
+       string sender = m.get_sender();
+       if (sender.empty())
+       {
+           throw FatalException();
+       }
+
+       DBusError err;
+       dbus_error_init(&err);
+
+       unsigned long uid = dbus_bus_get_unix_user(conn, sender.c_str(), &err);
+       if (dbus_error_is_set(&err))
+       {
+           dbus_error_free(&err);
+           throw FatalException();
+       }
+
+       return uid;
+    }
+
+
+    string
+    Connection::get_unix_username(const Message& m)
+    {
+       unsigned long userid = get_unix_userid(m);
+
+       long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+       char* buf = (char*) malloc(bufsize);
+       if (!buf)
+           throw FatalException();
+
+       struct passwd pwd;
+       struct passwd *result;
+       getpwuid_r(userid, &pwd, buf, bufsize, &result);
+       if (!result)
+           throw FatalException();
+
+       string username(pwd.pw_name);
+
+       free(buf);
+
+       return username;
+    }
+
+}
diff --git a/dbus/DBusConnection.h b/dbus/DBusConnection.h
new file mode 100644 (file)
index 0000000..d4a3c53
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+
+#ifndef SNAPPER_DBUSCONN_H
+#define SNAPPER_DBUSCONN_H
+
+
+#include <dbus/dbus.h>
+
+#include <boost/noncopyable.hpp>
+
+#include "DBusMessage.h"
+
+
+namespace DBus
+{
+
+    class Connection : boost::noncopyable
+    {
+    public:
+
+       Connection(DBusBusType type);
+       ~Connection();
+
+       DBusConnection* get_connection() { return conn; }
+
+       void request_name(const char* name, unsigned int flags);
+
+       void read_write(int timeout);
+
+       void send(Message& m);
+
+       Message send_and_reply_and_block(Message& m);
+
+       void add_match(const char* rule);
+
+       unsigned long get_unix_userid(const Message& m);
+       string get_unix_username(const Message& m);
+
+    private:
+
+       DBusConnection* conn;
+
+    };
+
+}
+
+
+#endif
diff --git a/dbus/DBusMessage.cc b/dbus/DBusMessage.cc
new file mode 100644 (file)
index 0000000..b9cc69f
--- /dev/null
@@ -0,0 +1,342 @@
+/*
+ * 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 <assert.h>
+#include <stdlib.h>
+
+#include "DBusMessage.h"
+
+
+namespace DBus
+{
+
+    Message::Message(DBusMessage* m)
+       : msg(m)
+    {
+       assert(msg);
+    }
+
+
+    Message::Message(const Message& m)
+       : msg(m.msg)
+    {
+       dbus_message_ref(msg);
+    }
+
+
+    Message::~Message()
+    {
+       dbus_message_unref(msg);
+    }
+
+
+    Message&
+    Message::operator=(const Message& m)
+    {
+       if (this != &m)
+       {
+           dbus_message_unref(msg);
+           msg = m.msg;
+           dbus_message_ref(msg);
+       }
+       return *this;
+    }
+
+
+    const char* TypeInfo<string>::signature = "s";
+
+
+    Hihi::Hihi(Message& msg)
+    {
+       DBusMessageIter args;
+       if (!dbus_message_iter_init(msg.get_message(), &args))
+       {
+           throw FatalException();
+       }
+       iters.push_back(args);
+    }
+
+
+    Hihi::~Hihi()
+    {
+       iters.pop_back();
+       assert(iters.empty());
+    }
+
+
+    void
+    Hihi::open_recurse()
+    {
+       DBusMessageIter iter2;
+       dbus_message_iter_recurse(top(), &iter2);
+       iters.push_back(iter2);
+    }
+
+
+    void
+    Hihi::close_recurse()
+    {
+       iters.pop_back();
+       dbus_message_iter_next(top());
+    }
+
+
+    Hoho::Hoho(Message& msg)
+    {
+       DBusMessageIter args;
+       dbus_message_iter_init_append(msg.get_message(), &args);
+       iters.push_back(args);
+    }
+
+
+    Hoho::~Hoho()
+    {
+       iters.pop_back();
+       assert(iters.empty());
+    }
+
+    void
+    Hoho::open_struct()
+    {
+       DBusMessageIter iter2;
+       dbus_message_iter_open_container(top(), DBUS_TYPE_STRUCT, NULL, &iter2);
+       iters.push_back(iter2);
+    }
+
+
+    void
+    Hoho::close_struct()
+    {
+       DBusMessageIter iter2 = *top();
+       iters.pop_back();
+       dbus_message_iter_close_container(top(), &iter2);
+    }
+
+
+    void
+    Hoho::open_array(const char* signature)
+    {
+       DBusMessageIter iter2;
+       dbus_message_iter_open_container(top(), DBUS_TYPE_ARRAY, signature, &iter2);
+       iters.push_back(iter2);
+    }
+
+
+    void
+    Hoho::close_array()
+    {
+       DBusMessageIter iter2 = *top();
+       iters.pop_back();
+       dbus_message_iter_close_container(top(), &iter2);
+    }
+
+
+    void
+    Hoho::open_dict_entry()
+    {
+       DBusMessageIter iter2;
+       dbus_message_iter_open_container(top(), DBUS_TYPE_DICT_ENTRY, 0, &iter2);
+       iters.push_back(iter2);
+    }
+
+
+    void
+    Hoho::close_dict_entry()
+    {
+       DBusMessageIter iter2 = *top();
+       iters.pop_back();
+       dbus_message_iter_close_container(top(), &iter2);
+    }
+
+
+    Hihi&
+    operator>>(Hihi& hihi, bool& data)
+    {
+       if (hihi.get_type() != DBUS_TYPE_BOOLEAN)
+           throw MarshallingException();
+
+       dbus_bool_t tmp;
+       dbus_message_iter_get_basic(hihi.top(), &tmp);
+       dbus_message_iter_next(hihi.top());
+       data = tmp;
+
+       return hihi;
+    }
+
+
+    Hoho&
+    operator<<(Hoho& hoho, bool data)
+    {
+       dbus_bool_t tmp = data;
+       dbus_message_iter_append_basic(hoho.top(), DBUS_TYPE_BOOLEAN, &tmp);
+
+       return hoho;
+    }
+
+
+    Hihi&
+    operator>>(Hihi& hihi, dbus_uint16_t& data)
+    {
+       if (hihi.get_type() != DBUS_TYPE_UINT16)
+           throw MarshallingException();
+
+       dbus_message_iter_get_basic(hihi.top(), &data);
+       dbus_message_iter_next(hihi.top());
+
+       return hihi;
+    }
+
+
+    Hoho&
+    operator<<(Hoho& hoho, dbus_uint16_t data)
+    {
+       dbus_message_iter_append_basic(hoho.top(), DBUS_TYPE_UINT16, &data);
+
+       return hoho;
+    }
+
+
+    Hihi&
+    operator>>(Hihi& hihi, dbus_uint32_t& data)
+    {
+       if (hihi.get_type() != DBUS_TYPE_UINT32)
+           throw MarshallingException();
+
+       dbus_message_iter_get_basic(hihi.top(), &data);
+       dbus_message_iter_next(hihi.top());
+
+       return hihi;
+    }
+
+
+    Hoho&
+    operator<<(Hoho& hoho, dbus_uint32_t data)
+    {
+       dbus_message_iter_append_basic(hoho.top(), DBUS_TYPE_UINT32, &data);
+
+       return hoho;
+    }
+
+
+    Hihi&
+    operator>>(Hihi& hihi, time_t& data)
+    {
+       if (hihi.get_type() != DBUS_TYPE_UINT32)
+           throw MarshallingException();
+
+       dbus_message_iter_get_basic(hihi.top(), &data);
+       dbus_message_iter_next(hihi.top());
+
+       return hihi;
+    }
+
+
+    Hoho&
+    operator<<(Hoho& hoho, time_t data)
+    {
+       dbus_message_iter_append_basic(hoho.top(), DBUS_TYPE_UINT32, &data);
+
+       return hoho;
+    }
+
+
+    Hoho&
+    operator<<(Hoho& hoho, const char* data)
+    {
+       dbus_message_iter_append_basic(hoho.top(), DBUS_TYPE_STRING, &data);
+
+       return hoho;
+    }
+
+    Hihi&
+    operator>>(Hihi& hihi, string& data)
+    {
+       if (hihi.get_type() != DBUS_TYPE_STRING)
+           throw MarshallingException();
+
+       const char* p = NULL;
+       dbus_message_iter_get_basic(hihi.top(), &p);
+       dbus_message_iter_next(hihi.top());
+       data = p;
+
+       return hihi;
+    }
+
+
+    Hoho&
+    operator<<(Hoho& hoho, const string& data)
+    {
+       const char* p = data.c_str();
+       dbus_message_iter_append_basic(hoho.top(), DBUS_TYPE_STRING, &p);
+
+       return hoho;
+    }
+
+
+    Hihi&
+    operator>>(Hihi& hihi, map<string, string>& data)
+    {
+       if (hihi.get_type() != DBUS_TYPE_ARRAY)
+           throw MarshallingException();
+
+       hihi.open_recurse();
+
+       while (hihi.get_type() != DBUS_TYPE_INVALID)
+       {
+           if (hihi.get_signature() != "{ss}")
+               throw MarshallingException();
+
+           hihi.open_recurse();
+
+           string k, v;
+           hihi >> k >> v;
+           data[k] = v;
+
+           hihi.close_recurse();
+       }
+
+       hihi.close_recurse();
+
+       return hihi;
+    }
+
+
+    Hoho&
+    operator<<(Hoho& hoho, const map<string, string>& data)
+    {
+       hoho.open_array("{ss}");
+
+       for (map<string, string>::const_iterator it = data.begin(); it != data.end() ; ++it)
+       {
+           hoho.open_dict_entry();
+
+           hoho << it->first << it->second;
+
+           hoho.close_dict_entry();
+       }
+
+       hoho.close_array();
+
+       return hoho;
+    }
+
+}
diff --git a/dbus/DBusMessage.h b/dbus/DBusMessage.h
new file mode 100644 (file)
index 0000000..4ed7243
--- /dev/null
@@ -0,0 +1,312 @@
+/*
+ * 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.
+ */
+
+
+#ifndef SNAPPER_DBUSMSG_H
+#define SNAPPER_DBUSMSG_H
+
+
+#include <dbus/dbus.h>
+
+#include <string>
+#include <vector>
+#include <list>
+#include <map>
+
+
+namespace DBus
+{
+    using std::string;
+    using std::vector;
+    using std::list;
+    using std::map;
+
+
+    struct Exception : public std::exception
+    {
+       explicit Exception() throw() {}
+       virtual const char* what() const throw() { return "dbus generic exception"; }
+    };
+
+
+    struct MarshallingException : public Exception
+    {
+       explicit MarshallingException() throw() {}
+       virtual const char* what() const throw() { return "dbus marshalling exception"; }
+    };
+
+
+    struct FatalException : public Exception
+    {
+       explicit FatalException() throw() {}
+       virtual const char* what() const throw() { return "dbus fatal exception"; }
+    };
+
+
+    class Message
+    {
+    public:
+
+       Message(DBusMessage* m);
+       Message(const Message& m);
+       ~Message();
+
+       Message& operator=(const Message& m);
+
+       DBusMessage* get_message() { return msg; }
+
+       int get_type() const { return dbus_message_get_type(msg); }
+       string get_member() const { return dbus_message_get_member(msg); }
+       string get_sender() const { return dbus_message_get_sender(msg); }
+       string get_path() const { return dbus_message_get_path(msg); }
+       string get_interface() const { return dbus_message_get_interface(msg); }
+       string get_error_name() const { return dbus_message_get_error_name(msg); }
+
+       bool is_method_call(const char* interface, const char* method) const
+       {
+           return dbus_message_is_method_call(msg, interface, method);
+       }
+
+       bool is_signal(const char* interface, const char* name) const
+       {
+           return dbus_message_is_signal(msg, interface, name);
+       }
+
+    private:
+
+       DBusMessage* msg;
+
+    };
+
+
+    class MessageMethodCall : public Message
+    {
+    public:
+
+       MessageMethodCall(const char* service, const char* object, const char* interface,
+                     const char* method)
+           : Message(dbus_message_new_method_call(service, object, interface, method))
+       {
+       }
+
+    };
+
+
+    class MessageMethodReturn : public Message
+    {
+    public:
+
+       MessageMethodReturn(Message& m)
+           : Message(dbus_message_new_method_return(m.get_message()))
+       {
+           if (m.get_type() != DBUS_MESSAGE_TYPE_METHOD_CALL)
+               throw FatalException();
+       }
+
+    };
+
+
+    class MessageError : public Message
+    {
+    public:
+
+       MessageError(Message& m, const char* error_msg, const char* error_code)
+           : Message(dbus_message_new_error(m.get_message(), error_msg, error_code))
+       {
+           if (m.get_type() != DBUS_MESSAGE_TYPE_METHOD_CALL)
+               throw FatalException();
+       }
+
+    };
+
+
+    class MessageSignal : public Message
+    {
+    public:
+
+       MessageSignal(const char* path, const char* interface, const char* name)
+           : Message(dbus_message_new_signal(path, interface, name))
+       {
+       }
+
+    };
+
+
+    template <typename Type> struct TypeInfo {};
+
+    template <> struct TypeInfo<string> { static const char* signature; };
+
+
+    class Marshalling
+    {
+
+    public:
+
+       DBusMessageIter* top() { return &iters.back(); }
+
+       int get_type() { return dbus_message_iter_get_arg_type(top()); }
+       string get_signature() { return dbus_message_iter_get_signature(top()); }
+
+    protected:
+
+       list<DBusMessageIter> iters;
+
+    };
+
+
+    class Hihi : public Marshalling
+    {
+
+    public:
+
+       Hihi(Message& msg);
+       ~Hihi();
+
+       void open_recurse();
+       void close_recurse();
+
+    };
+
+
+    class Hoho : public Marshalling
+    {
+
+    public:
+
+       Hoho(Message& msg);
+       ~Hoho();
+
+       void open_struct();
+       void close_struct();
+
+       void open_array(const char* signature);
+       void close_array();
+
+       void open_dict_entry();
+       void close_dict_entry();
+
+    };
+
+
+    Hihi& operator>>(Hihi& hihi, bool& data);
+    Hoho& operator<<(Hoho& hoho, bool data);
+
+    Hihi& operator>>(Hihi& hihi, dbus_uint16_t& data);
+    Hoho& operator<<(Hoho& hoho, dbus_uint16_t data);
+
+    Hihi& operator>>(Hihi& hihi, dbus_uint32_t& data);
+    Hoho& operator<<(Hoho& hoho, dbus_uint32_t data);
+
+    Hihi& operator>>(Hihi& hihi, time_t& data);
+    Hoho& operator<<(Hoho& hoho, time_t data);
+
+    Hoho& operator<<(Hoho& hoho, const char* data);
+
+    Hihi& operator>>(Hihi& hihi, string& data);
+    Hoho& operator<<(Hoho& hoho, const string& data);
+
+    Hihi& operator>>(Hihi& hihi, map<string, string>& data);
+    Hoho& operator<<(Hoho& hoho, const map<string, string>& data);
+
+
+    template <typename Type>
+    Hihi& operator>>(Hihi& hihi, vector<Type>& data)
+    {
+       if (hihi.get_type() != DBUS_TYPE_ARRAY)
+           throw MarshallingException();
+
+       hihi.open_recurse();
+
+       while (hihi.get_type() != DBUS_TYPE_INVALID)
+       {
+           if (hihi.get_signature() != TypeInfo<Type>::signature)
+               throw MarshallingException();
+
+           Type tmp;
+           hihi >> tmp;
+           data.push_back(tmp);
+       }
+
+       hihi.close_recurse();
+
+       return hihi;
+    }
+
+
+    template <typename Type>
+    Hoho& operator<<(Hoho& hoho, const vector<Type>& data)
+    {
+       hoho.open_array(TypeInfo<Type>::signature);
+
+       for (typename vector<Type>::const_iterator it = data.begin(); it != data.end(); ++it)
+       {
+           hoho << *it;
+       }
+
+       hoho.close_array();
+
+       return hoho;
+    }
+
+
+    template <typename Type>
+    Hihi& operator>>(Hihi& hihi, list<Type>& data)
+    {
+       if (hihi.get_type() != DBUS_TYPE_ARRAY)
+           throw MarshallingException();
+
+       hihi.open_recurse();
+
+       while (hihi.get_type() != DBUS_TYPE_INVALID)
+       {
+           if (hihi.get_signature() != TypeInfo<Type>::signature)
+               throw MarshallingException();
+
+           Type tmp;
+           hihi >> tmp;
+           data.push_back(tmp);
+       }
+
+       hihi.close_recurse();
+
+       return hihi;
+    }
+
+
+    template <typename Type>
+    Hoho& operator<<(Hoho& hoho, const list<Type>& data)
+    {
+       hoho.open_array(TypeInfo<Type>::signature);
+
+       for (typename list<Type>::const_iterator it = data.begin(); it != data.end(); ++it)
+       {
+           hoho << *it;
+       }
+
+       hoho.close_array();
+
+       return hoho;
+    }
+
+}
+
+
+#endif
diff --git a/dbus/Makefile.am b/dbus/Makefile.am
new file mode 100644 (file)
index 0000000..2f40f4b
--- /dev/null
@@ -0,0 +1,12 @@
+#
+# Makefile.am for snapper/tools/utils/dbus
+#
+
+INCLUDES = -I$(top_srcdir) -I/usr/include/dbus-1.0 -I/usr/lib/dbus-1.0/include
+
+noinst_LTLIBRARIES = libdbus.la
+
+libdbus_la_SOURCES =                                   \
+       DBusConnection.cc       DBusConnection.h        \
+       DBusMessage.cc          DBusMessage.h
+
diff --git a/examples/python/comparison.py b/examples/python/comparison.py
new file mode 100755 (executable)
index 0000000..ffb6f55
--- /dev/null
@@ -0,0 +1,31 @@
+#!/usr/bin/python
+
+import dbus
+
+bus = dbus.SystemBus()
+
+snapper = dbus.Interface(bus.get_object('org.opensuse.snapper', '/org/opensuse/snapper'),
+                         dbus_interface='org.opensuse.snapper')
+
+
+num_files = snapper.CreateComparison("root", 1306, 1307)
+
+print num_files
+
+
+files = snapper.GetFiles("root", 1306, 1307)
+
+for file in files:
+    print file[0], file[1], file[2]
+
+
+undo = [ [ "/hello", False ], [ "/world", True ] ]
+
+snapper.SetUndo("root", 1306, 1307, undo)
+
+
+files = snapper.GetFiles("root", 1306, 1307)
+
+for file in files:
+    print file[0], file[1], file[2]
+
diff --git a/examples/python/create.py b/examples/python/create.py
new file mode 100755 (executable)
index 0000000..9360dfc
--- /dev/null
@@ -0,0 +1,12 @@
+#!/usr/bin/python
+
+import dbus
+
+bus = dbus.SystemBus()
+
+snapper = dbus.Interface(bus.get_object('org.opensuse.snapper', '/org/opensuse/snapper'),
+                         dbus_interface='org.opensuse.snapper')
+
+
+print snapper.CreateSnapshot("root", 0, 0, "test", "", { "tid" : "456" })
+
diff --git a/examples/python/debug.py b/examples/python/debug.py
new file mode 100755 (executable)
index 0000000..ef3b981
--- /dev/null
@@ -0,0 +1,15 @@
+#!/usr/bin/python
+
+import dbus
+
+bus = dbus.SystemBus()
+
+snapper = dbus.Interface(bus.get_object('org.opensuse.snapper', '/org/opensuse/snapper'),
+                         dbus_interface='org.opensuse.snapper')
+
+
+lines = snapper.Debug()
+
+for line in lines:
+    print line
+
diff --git a/examples/python/delete.py b/examples/python/delete.py
new file mode 100755 (executable)
index 0000000..68b317c
--- /dev/null
@@ -0,0 +1,12 @@
+#!/usr/bin/python
+
+import dbus
+
+bus = dbus.SystemBus()
+
+snapper = dbus.Interface(bus.get_object('org.opensuse.snapper', '/org/opensuse/snapper'),
+                         dbus_interface='org.opensuse.snapper')
+
+
+snapper.DeleteSnapshot("root", 1450)
+
diff --git a/examples/python/list-configs.py b/examples/python/list-configs.py
new file mode 100755 (executable)
index 0000000..7decf7b
--- /dev/null
@@ -0,0 +1,15 @@
+#!/usr/bin/python
+
+import dbus
+
+bus = dbus.SystemBus()
+
+snapper = dbus.Interface(bus.get_object('org.opensuse.snapper', '/org/opensuse/snapper'),
+                         dbus_interface='org.opensuse.snapper')
+
+
+configs = snapper.ListConfigs()
+
+for config in configs:
+    print config[0], config[1]
+
diff --git a/examples/python/list.py b/examples/python/list.py
new file mode 100755 (executable)
index 0000000..71d9b12
--- /dev/null
@@ -0,0 +1,18 @@
+#!/usr/bin/python
+
+import dbus
+
+bus = dbus.SystemBus()
+
+snapper = dbus.Interface(bus.get_object('org.opensuse.snapper', '/org/opensuse/snapper'),
+                         dbus_interface='org.opensuse.snapper')
+
+
+snapshots = snapper.ListSnapshots("root")
+
+for snapshot in snapshots:
+    print snapshot[0], snapshot[1], snapshot[2], snapshot[3],
+    for k, v in snapshot[4].items():
+        print "%s=%s" % (k, v),
+    print
+
diff --git a/examples/python/lock-config.py b/examples/python/lock-config.py
new file mode 100755 (executable)
index 0000000..b3de3b6
--- /dev/null
@@ -0,0 +1,17 @@
+#!/usr/bin/python
+
+from time import sleep
+import dbus
+
+bus = dbus.SystemBus()
+
+snapper = dbus.Interface(bus.get_object('org.opensuse.snapper', '/org/opensuse/snapper'),
+                         dbus_interface='org.opensuse.snapper')
+
+
+snapper.LockConfig("root")
+
+sleep(10)
+
+snapper.UnlockConfig("root")
+
diff --git a/examples/qt/client-qt.cc b/examples/qt/client-qt.cc
new file mode 100644 (file)
index 0000000..6ed6552
--- /dev/null
@@ -0,0 +1,101 @@
+
+// g++ client-qt.cc -o client-qt -Wall -O2 -lQtCore -lQtDBus
+
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QDebug>
+#include <QtDBus/QtDBus>
+
+
+enum SnapshotType { SINGLE, PRE, POST };
+
+struct Snapshot
+{
+    unsigned int num;
+    SnapshotType type;
+    unsigned int date;
+    QString description;
+    QMap<QString, QString> userdata;
+};
+
+
+Q_DECLARE_METATYPE(Snapshot)
+
+
+QDBusArgument& operator<<(QDBusArgument& argument, const Snapshot& mystruct)
+{
+    argument.beginStructure();
+    argument << mystruct.num << static_cast<unsigned short>(mystruct.type) << mystruct.date
+            << mystruct.description << mystruct.userdata;
+    argument.endStructure();
+    return argument;
+}
+
+
+const QDBusArgument& operator>>(const QDBusArgument& argument, Snapshot& mystruct)
+{
+    unsigned short tmp1;
+
+    argument.beginStructure();
+    argument >> mystruct.num >> tmp1 >> mystruct.date >> mystruct.description >> mystruct.userdata;
+    argument.endStructure();
+
+    mystruct.type = static_cast<SnapshotType>(tmp1);
+
+    return argument;
+}
+
+
+void
+command_list_snapshots()
+{
+    QDBusInterface dbus_iface("org.opensuse.snapper", "/org/opensuse/snapper",
+                             "org.opensuse.snapper", QDBusConnection::systemBus());
+
+    QDBusMessage reply = dbus_iface.call("ListSnapshots", "root");
+    // qDebug() << reply;
+    // qDebug() << (reply.type() == QDBusMessage::ReplyMessage);
+
+    QList<QVariant> args = reply.arguments();
+    // qDebug() << args.size();
+
+    QDBusArgument arg = args.at(0).value<QDBusArgument>();
+    // qDebug() << arg.currentSignature();
+
+    QList<Snapshot> snapshots;
+    arg >> snapshots;
+
+    QListIterator<Snapshot> it(snapshots);
+    while (it.hasNext())
+    {
+       const Snapshot& snapshot = it.next();
+       printf("%d %d %d %s", snapshot.num, snapshot.type, snapshot.date,
+              qPrintable(snapshot.description));
+       QMapIterator<QString, QString> it2(snapshot.userdata);
+       while (it2.hasNext())
+       {
+           it2.next();
+           printf(" %s=%s", qPrintable(it2.key()), qPrintable(it2.value()));
+       }
+       printf("\n");
+    }
+}
+
+
+int
+main(int argc, char** argv)
+{
+    QCoreApplication app(argc, argv);
+
+    qDBusRegisterMetaType<Snapshot>();
+
+    if (!QDBusConnection::systemBus().isConnected())
+    {
+       fprintf(stderr, "Cannot connect to the D-Bus system bus.\n");
+       return 1;
+    }
+
+    command_list_snapshots();
+
+    return 0;
+}
diff --git a/server/.gitignore b/server/.gitignore
new file mode 100644 (file)
index 0000000..7d082f2
--- /dev/null
@@ -0,0 +1,2 @@
+*.o
+snapperd
diff --git a/server/Client.cc b/server/Client.cc
new file mode 100644 (file)
index 0000000..8cd2e25
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * 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 <algorithm>
+
+
+
+#include "Client.h"
+#include "MetaSnapper.h"
+
+
+template <typename ListType, typename Type>
+bool contains(const ListType& l, const Type& value)
+{
+    return find(l.begin(), l.end(), value) != l.end();
+}
+
+
+Client::Client(const string& name)
+    : name(name)
+{
+}
+
+
+Client::~Client()
+{
+}
+
+
+Comparison*
+Client::find_comparison(Snapper* snapper, Snapshots::const_iterator snapshot1,
+                       Snapshots::const_iterator snapshot2)
+{
+    for (list<Comparison*>::iterator it = comparisons.begin(); it != comparisons.end(); ++it)
+    {
+       if ((*it)->getSnapper() == snapper && (*it)->getSnapshot1() == snapshot1 &&
+           (*it)->getSnapshot2() == snapshot2)
+           return *it;
+    }
+
+    return NULL;
+}
+
+
+Comparison*
+Client::find_comparison(const string& config_name, unsigned int number1, unsigned int number2)
+{
+    Snapper* snapper = getSnapper(config_name);
+    Snapshots& snapshots = snapper->getSnapshots();
+    Snapshots::const_iterator snapshot1 = snapshots.find(number1);
+    Snapshots::const_iterator snapshot2 = snapshots.find(number2);
+
+    return find_comparison(snapper, snapshot1, snapshot2);
+}
+
+
+void
+Client::add_lock(const string& config_name)
+{
+    locks.insert(config_name);
+}
+
+
+void
+Client::remove_lock(const string& config_name)
+{
+    locks.erase(config_name);
+}
+
+
+bool
+Client::has_lock(const string& config_name) const
+{
+    return contains(locks, config_name);
+}
+
+
+Clients::iterator
+Clients::find(const string& name)
+{
+    for (iterator it = entries.begin(); it != entries.end(); ++it)
+       if (it->name == name)
+           return it;
+
+    return entries.end();
+}
+
+
+void
+Clients::add(const string& name)
+{
+    entries.push_back(Client(name));
+}
+
+
+void
+Clients::remove(const string& name)
+{
+    iterator it = find(name);
+    if (it != entries.end())
+       entries.erase(it);
+}
diff --git a/server/Client.h b/server/Client.h
new file mode 100644 (file)
index 0000000..73e904a
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+
+#ifndef SNAPPER_CLIENT_H
+#define SNAPPER_CLIENT_H
+
+
+#include <string>
+#include <list>
+#include <set>
+
+#include <snapper/Snapper.h>
+#include <snapper/Snapshot.h>
+#include <snapper/Factory.h>
+#include <snapper/Comparison.h>
+
+
+using namespace std;
+using namespace snapper;
+
+
+class Client
+{
+public:
+
+    Client(const string& name);
+    ~Client();
+
+    Comparison* find_comparison(const string& config_name, unsigned int number1,
+                               unsigned int number2);
+
+    Comparison* find_comparison(Snapper* snapper, Snapshots::const_iterator snapshot1,
+                               Snapshots::const_iterator snapshot2);
+    
+    void add_lock(const string& config_name);
+    void remove_lock(const string& config_name);
+    bool has_lock(const string& config_name) const;
+
+    string name;
+
+    list<Comparison*> comparisons;
+
+    set<string> locks;
+
+};
+
+
+class Clients
+{
+public:
+
+    typedef list<Client>::iterator iterator;
+    typedef list<Client>::const_iterator const_iterator;
+
+    iterator begin() { return entries.begin(); }
+    const_iterator begin() const { return entries.begin(); }
+    
+    iterator end() { return entries.end(); }
+    const_iterator end() const { return entries.end(); }
+
+    bool empty() const { return entries.empty(); }
+
+    iterator find(const string& name);
+
+    void add(const string& name);
+    void remove(const string& name);
+
+private:
+
+    list<Client> entries;
+
+};
+    
+
+#endif
diff --git a/server/Job.cc b/server/Job.cc
new file mode 100644 (file)
index 0000000..7dcd4cd
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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 "Job.h"
+
+
+void
+Jobs::add(Job* job)
+{
+    job->start();
+    entries.push_back(job);
+}
+
+
+void
+Jobs::handle()
+{
+    list<Job*>::iterator it = entries.begin();
+    while (it != entries.end())
+    {
+       if (!(*it)->is_running())
+       {
+           (*it)->done();
+           delete *it;
+           it = entries.erase(it);
+       }
+       else
+       {
+           ++it;
+       }
+    }
+}
diff --git a/server/Job.h b/server/Job.h
new file mode 100644 (file)
index 0000000..642c702
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+
+#ifndef SNAPPER_JOB_H
+#define SNAPPER_JOB_H
+
+
+#include <list>
+#include <boost/thread.hpp>
+
+
+using namespace std;
+using namespace boost::posix_time;
+
+
+struct Job
+{
+public:
+
+    virtual ~Job() {}
+
+    void start() { t = boost::thread(boost::ref(*this)); }
+
+    bool is_running() { return !t.timed_join(seconds(0)); }
+
+    virtual void done() = 0;
+
+    virtual void operator()() = 0;
+
+protected:
+
+    boost::thread t;
+
+};
+
+
+class Jobs
+{
+public:
+
+    void add(Job* job);
+
+    void handle();
+
+    size_t size() const { return entries.size(); }
+    bool empty() const { return entries.empty(); }
+
+private:
+
+    list<Job*> entries;
+
+};
+
+
+#endif
diff --git a/server/Makefile.am b/server/Makefile.am
new file mode 100644 (file)
index 0000000..4f39cf8
--- /dev/null
@@ -0,0 +1,12 @@
+#
+# Makefile.am for snapper/tools/server
+#
+
+INCLUDES = -I$(top_srcdir)  -I/usr/include/dbus-1.0 -I/usr/lib/dbus-1.0/include
+
+
+sbin_PROGRAMS = snapperd
+
+snapperd_SOURCES = snapperd.cc Client.cc Job.cc MetaSnapper.cc Types.cc
+snapperd_LDADD = ../snapper/libsnapper.la ../dbus/libdbus.la -ldbus-1 -lboost_thread
+
diff --git a/server/MetaSnapper.cc b/server/MetaSnapper.cc
new file mode 100644 (file)
index 0000000..47fec8a
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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 "MetaSnapper.h"
+
+
+Snapper*
+getSnapper(const string& config_name)
+{
+    for (list<Snapper*>::iterator it = snappers.begin(); it != snappers.end(); ++it)
+       if ((*it)->configName() == config_name)
+           return *it;
+
+    Snapper* tmp = new Snapper(config_name);
+    snappers.push_back(tmp);
+    return tmp;
+}
+
+
+list<Snapper*> snappers;
+
diff --git a/server/MetaSnapper.h b/server/MetaSnapper.h
new file mode 100644 (file)
index 0000000..0d57107
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+
+#ifndef SNAPPER_META_SNAPPER_H
+#define SNAPPER_META_SNAPPER_H
+
+
+#include <snapper/Snapper.h>
+#include <snapper/Snapshot.h>
+#include <snapper/Comparison.h>
+
+
+using namespace std;
+using namespace snapper;
+
+
+extern list<Snapper*> snappers;
+
+
+Snapper*
+getSnapper(const string& config_name);
+
+
+#endif
diff --git a/server/Types.cc b/server/Types.cc
new file mode 100644 (file)
index 0000000..798b4e6
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * 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 "Types.h"
+
+
+namespace DBus
+{
+    const char* TypeInfo<ConfigInfo>::signature = "(ss)";
+    const char* TypeInfo<Snapshot>::signature = "(uqusa{ss})";
+    const char* TypeInfo<File>::signature = "(ssb)";
+    const char* TypeInfo<Undo>::signature = "(sb)";
+
+
+    Hoho&
+    operator<<(Hoho& hoho, const ConfigInfo& data)
+    {
+       hoho.open_struct();
+       hoho << data.config_name << data.subvolume;
+       hoho.close_struct();
+       return hoho;
+    }
+
+
+    Hihi&
+    operator>>(Hihi& hihi, SnapshotType& data)
+    {
+       dbus_uint16_t tmp;
+       hihi >> tmp;
+       data = static_cast<SnapshotType>(tmp);
+       return hihi;
+    }
+
+
+    Hoho&
+    operator<<(Hoho& hoho, SnapshotType data)
+    {
+       hoho << static_cast<dbus_uint16_t>(data);
+       return hoho;
+    }
+
+
+    Hoho&
+    operator<<(Hoho& hoho, const Snapshot& data)
+    {
+       hoho.open_struct();
+       hoho << data.getNum() << data.getType() << data.getDate()
+            << data.getDescription() << data.getUserdata();
+       hoho.close_struct();
+       return hoho;
+    }
+
+
+    Hoho&
+    operator<<(Hoho& hoho, const Snapshots& data)
+    {
+       hoho.open_array(TypeInfo<Snapshot>::signature);
+       for (Snapshots::const_iterator it = data.begin(); it != data.end(); ++it)
+           hoho << *it;
+       hoho.close_array();
+       return hoho;
+    }
+
+
+    Hoho&
+    operator<<(Hoho& hoho, const File& data)
+    {
+       hoho.open_struct();
+       hoho << data.getName() << statusToString(data.getPreToPostStatus()) << data.getUndo();
+       hoho.close_struct();
+       return hoho;
+    }
+
+
+    Hoho&
+    operator<<(Hoho& hoho, const Files& data)
+    {
+       hoho.open_array(TypeInfo<File>::signature);
+       for (Files::const_iterator it = data.begin(); it != data.end(); ++it)
+           hoho << *it;
+       hoho.close_array();
+       return hoho;
+    }
+
+
+    Hihi&
+    operator>>(Hihi& hihi, Undo& data)
+    {
+       hihi.open_recurse();
+       hihi >> data.filename >> data.undo;
+       hihi.close_recurse();
+       return hihi;
+    }
+
+
+    Hoho&
+    operator<<(Hoho& hoho, const Undo& data)
+    {
+       hoho.open_struct();
+       hoho << data.filename << data.undo;
+       hoho.close_struct();
+       return hoho;
+    }
+
+}
+
diff --git a/server/Types.h b/server/Types.h
new file mode 100644 (file)
index 0000000..735716d
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * 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 <unistd.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dbus/dbus.h>
+
+#include <string>
+#include <list>
+
+#include <snapper/Snapper.h>
+#include <snapper/Snapshot.h>
+#include <snapper/File.h>
+
+using std::string;
+using std::list;
+
+using namespace snapper;
+
+#include "dbus/DBusMessage.h"
+
+
+struct Undo
+{
+    Undo() {}
+    Undo(const string& filename, bool undo) : filename(filename), undo(undo) {}
+
+    string filename;
+    bool undo;
+};
+
+
+namespace DBus
+{
+    template <> struct TypeInfo<ConfigInfo> { static const char* signature; };
+    template <> struct TypeInfo<Snapshot> { static const char* signature; };
+    template <> struct TypeInfo<File> { static const char* signature; };
+    template <> struct TypeInfo<Undo> { static const char* signature; };
+
+    Hoho& operator<<(Hoho& hoho, const ConfigInfo& data);
+
+    Hihi& operator>>(Hihi& hihi, SnapshotType& data);
+    Hoho& operator<<(Hoho& hoho, SnapshotType data);
+
+    Hoho& operator<<(Hoho& hoho, const Snapshot& data);
+
+    Hoho& operator<<(Hoho& hoho, const Snapshots& data);
+
+    Hoho& operator<<(Hoho& hoho, const File& data);
+
+    Hoho& operator<<(Hoho& hoho, const Files& data);
+
+    Hihi& operator>>(Hihi& hihi, Undo& data);
+    Hoho& operator<<(Hoho& hoho, const Undo& data);
+
+};
diff --git a/server/snapperd.cc b/server/snapperd.cc
new file mode 100644 (file)
index 0000000..6db57da
--- /dev/null
@@ -0,0 +1,847 @@
+/*
+ * 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 <unistd.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dbus/dbus.h>
+
+#include <string>
+#include <iostream>
+#include <boost/algorithm/string.hpp>
+
+#include <snapper/Snapper.h>
+#include <snapper/Snapshot.h>
+#include <snapper/Comparison.h>
+#include <snapper/Log.h>
+
+#include "dbus/DBusConnection.h"
+#include "dbus/DBusMessage.h"
+
+#include "MetaSnapper.h"
+#include "Client.h"
+#include "Job.h"
+#include "Types.h"
+
+
+using namespace std;
+using namespace snapper;
+
+
+Clients clients;
+
+Jobs jobs;
+
+
+void
+reply_to_introspect(DBus::Connection& conn, DBus::Message& msg)
+{
+    const char* introspect =
+       DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE "\n"
+       "<node name='/org/opensuse/snapper'>\n"
+       "  <interface name='" DBUS_INTERFACE_INTROSPECTABLE "'>\n"
+       "    <method name='Introspect'>\n"
+       "      <arg name='xml_data' type='s' direction='out'/>\n"
+       "    </method>\n"
+       "  </interface>\n"
+       "  <interface name='org.opensuse.snapper'>\n"
+
+       "    <method name='ListConfigs'>\n"
+       "      <arg name='configs' type='v' direction='out'/>\n"
+       "    </method>\n"
+
+       "    <method name='CreateConfig'>\n"
+       "      <arg name='config-name' type='s' direction='in'/>\n"
+       "      <arg name='subvolume' type='s' direction='in'/>\n"
+       "    </method>\n"
+
+       "    <method name='DeleteConfig'>\n"
+       "      <arg name='config-name' type='s' direction='in'/>\n"
+       "    </method>\n"
+
+       "    <method name='LockConfig'>\n"
+       "      <arg name='config-name' type='s' direction='in'/>\n"
+       "    </method>\n"
+
+       "    <method name='UnlockConfig'>\n"
+       "      <arg name='config-name' type='s' direction='in'/>\n"
+       "    </method>\n"
+
+       "    <method name='ListSnapshots'>\n"
+       "      <arg name='config-name' type='s' direction='in'/>\n"
+       "      <arg name='snapshots' type='v' direction='out'/>\n"
+       "    </method>\n"
+
+       "    <method name='CreateSnapshot'>\n"
+       "      <arg name='config-name' type='s' direction='in'/>\n"
+       "      <arg name='type' type='q' direction='in'/>\n"
+       "      <arg name='pre-number' type='u' direction='in'/>\n"
+       "      <arg name='description' type='s' direction='in'/>\n"
+       "      <arg name='cleanup' type='s' direction='in'/>\n"
+       "      <arg name='userdata' type='a{ss}' direction='in'/>\n"
+       "      <arg name='number' type='u' direction='out'/>\n"
+       "    </method>\n"
+
+       "    <method name='DeleteSnapshot'>\n"
+       "      <arg name='config-name' type='s' direction='in'/>\n"
+       "      <arg name='number' type='u' direction='in'/>\n"
+       "    </method>\n"
+
+       "    <method name='CreateComparison'>\n"
+       "      <arg name='config-name' type='s' direction='in'/>\n"
+       "      <arg name='number1' type='u' direction='in'/>\n"
+       "      <arg name='number2' type='u' direction='in'/>\n"
+       "      <arg name='num-files' type='u' direction='out'/>\n"
+       "    </method>\n"
+
+       "    <method name='GetFiles'>\n"
+       "      <arg name='config-name' type='s' direction='in'/>\n"
+       "      <arg name='number1' type='u' direction='in'/>\n"
+       "      <arg name='number2' type='u' direction='in'/>\n"
+       "      <arg name='files' type='v' direction='out'/>\n"
+       "    </method>\n"
+
+       "    <method name='SetUndo'>\n"
+       "      <arg name='config-name' type='s' direction='in'/>\n"
+       "      <arg name='number1' type='u' direction='in'/>\n"
+       "      <arg name='number2' type='u' direction='in'/>\n"
+       "      <arg name='files' type='a(sb)' direction='in'/>\n"
+       "    </method>\n"
+
+       "    <method name='GetDiff'>\n"
+       "      <arg name='config-name' type='s' direction='in'/>\n"
+       "      <arg name='number1' type='u' direction='in'/>\n"
+       "      <arg name='number2' type='u' direction='in'/>\n"
+       "      <arg name='filename' type='s' direction='in'/>\n"
+       "      <arg name='options' type='s' direction='in'/>\n"
+       "      <arg name='diff' type='v' direction='out'/>\n"
+       "    </method>\n"
+
+       "  </interface>\n"
+       "</node>\n";
+
+    DBus::MessageMethodReturn reply(msg);
+
+    DBus::Hoho hoho(reply);
+    hoho << introspect;
+
+    conn.send(reply);
+}
+
+
+struct Permissions : public std::exception
+{
+    explicit Permissions() throw() {}
+    virtual const char* what() const throw() { return "permissions"; }
+};
+
+
+void
+check_permission(DBus::Connection& conn, DBus::Message& msg)
+{
+    unsigned long uid = conn.get_unix_userid(msg);
+    if (uid == 0)
+       return;
+
+    throw Permissions();
+}
+
+
+void
+check_permission(DBus::Connection& conn, DBus::Message& msg, const string& config_name)
+{
+    list<ConfigInfo> config_infos = Snapper::getConfigs();
+
+    for (list<ConfigInfo>::const_iterator it = config_infos.begin();
+        it != config_infos.end(); ++it)
+    {
+       if (it->config_name == config_name)
+       {
+           unsigned long uid = conn.get_unix_userid(msg);
+           if (uid == 0)
+               return;
+
+           string username = conn.get_unix_username(msg);
+           if (find(it->users.begin(), it->users.end(), username) != it->users.end())
+               return;
+
+           break;
+       }
+    }
+
+    throw Permissions();
+}
+
+
+struct Lock : public std::exception
+{
+    explicit Lock() throw() {}
+    virtual const char* what() const throw() { return "locked"; }
+};
+
+
+void
+check_lock(DBus::Connection& conn, DBus::Message& msg, const string& config_name)
+{
+    for (Clients::const_iterator it = clients.begin(); it != clients.end(); ++it)
+    {
+       if (it->name == msg.get_sender())
+           continue;
+
+       if (it->has_lock(config_name))
+           throw Lock();
+    }
+}
+
+
+void
+send_signal_config_created(DBus::Connection& conn, const string& config_name,
+                          const string& subvolume)
+{
+    cout << "sending signal ConfigCreated" << endl;
+
+    DBus::MessageSignal msg("/org/opensuse/snapper", "org.opensuse.snapper", "ConfigCreated");
+
+    DBus::Hoho hoho(msg);
+    hoho << config_name;
+
+    conn.send(msg);
+}
+
+
+void
+send_signal_snapshot_created(DBus::Connection& conn, const string& config_name,
+                            unsigned int number)
+{
+    cout << "sending signal SnapshotCreated" << endl;
+
+    DBus::MessageSignal msg("/org/opensuse/snapper", "org.opensuse.snapper", "SnapshotCreated");
+
+    DBus::Hoho hoho(msg);
+    hoho << config_name << number;
+
+    conn.send(msg);
+}
+
+
+void
+send_signal_snapshot_deleted(DBus::Connection& conn, const string& config_name,
+                            unsigned int number)
+{
+    cout << "sending signal SnapshotDeleted" << endl;
+
+    DBus::MessageSignal msg("/org/opensuse/snapper", "org.opensuse.snapper", "SnapshotDeleted");
+
+    DBus::Hoho hoho(msg);
+    hoho << config_name << number;
+
+    conn.send(msg);
+}
+
+
+void
+reply_to_command_list_configs(DBus::Connection& conn, DBus::Message& msg)
+{
+    cout << "command ListConfigs" << endl;
+
+    list<ConfigInfo> config_infos = Snapper::getConfigs();
+
+    DBus::MessageMethodReturn reply(msg);
+
+    DBus::Hoho hoho(reply);
+    hoho << config_infos;
+
+    conn.send(reply);
+}
+
+
+void
+reply_to_command_create_config(DBus::Connection& conn, DBus::Message& msg)
+{
+    cout << "command CreateConfigs" << endl;
+
+    check_permission(conn, msg);
+
+    string config_name;
+    string subvolume;
+
+    DBus::Hihi hihi(msg);
+    hihi >> config_name >> subvolume;
+
+    DBus::MessageMethodReturn reply(msg);
+
+    conn.send(reply);
+
+    send_signal_config_created(conn, config_name, subvolume);
+}
+
+
+void
+reply_to_command_lock_config(DBus::Connection& conn, DBus::Message& msg)
+{
+    cout << "command LockConfig" << endl;
+
+    string config_name;
+
+    DBus::Hihi hihi(msg);
+    hihi >> config_name;
+
+    cout << "Method called with " << config_name << endl;
+
+    check_permission(conn, msg, config_name);
+
+    string sender = msg.get_sender();
+
+    Clients::iterator it = clients.find(sender);
+    it->add_lock(config_name);
+
+    DBus::MessageMethodReturn reply(msg);
+    conn.send(reply);
+}
+
+
+void
+reply_to_command_unlock_config(DBus::Connection& conn, DBus::Message& msg)
+{
+    cout << "command UnlockConfig" << endl;
+
+    string config_name;
+
+    DBus::Hihi hihi(msg);
+    hihi >> config_name;
+
+    cout << "Method called with " << config_name << endl;
+
+    check_permission(conn, msg, config_name);
+
+    string sender = msg.get_sender();
+
+    Clients::iterator it = clients.find(sender);
+    it->remove_lock(config_name);
+
+    DBus::MessageMethodReturn reply(msg);
+    conn.send(reply);
+}
+
+
+void
+reply_to_command_list_snapshots(DBus::Connection& conn, DBus::Message& msg)
+{
+    cout << "command ListSnapshots" << endl;
+
+    string config_name;
+
+    DBus::Hihi hihi(msg);
+    hihi >> config_name;
+
+    cout << "Method called with " << config_name << endl;
+
+    check_permission(conn, msg, config_name);
+
+    DBus::MessageMethodReturn reply(msg);
+
+    Snapper* snapper = getSnapper(config_name);
+
+    DBus::Hoho hoho(reply);
+    hoho << snapper->getSnapshots();
+
+    conn.send(reply);
+}
+
+
+void
+reply_to_command_create_snapshot(DBus::Connection& conn, DBus::Message& msg)
+{
+    cout << "command CreateSnapshots" << endl;
+
+    string config_name;
+    SnapshotType type;
+    unsigned int prenum;
+    string description;
+    string cleanup;
+    map<string, string> userdata;
+
+    DBus::Hihi hihi(msg);
+    hihi >> config_name >> type >> prenum >> description >> cleanup >> userdata;
+
+    cout << "Method called with " << config_name << endl;
+
+    check_permission(conn, msg, config_name);
+
+    DBus::MessageMethodReturn reply(msg);
+
+    Snapper* snapper = getSnapper(config_name);
+
+    // TODO type
+    Snapshots::iterator snap1 = snapper->createSingleSnapshot(description);
+    snap1->setCleanup(cleanup);
+    snap1->setUserdata(userdata);
+    snap1->flushInfo();
+
+    DBus::Hoho hoho(reply);
+    hoho << snap1->getNum();
+
+    conn.send(reply);
+
+    send_signal_snapshot_created(conn, config_name, snap1->getNum());
+}
+
+
+void
+reply_to_command_delete_snapshot(DBus::Connection& conn, DBus::Message& msg)
+{
+    cout << "command DeleteSnapshots" << endl;
+
+    string config_name;
+    unsigned int number;
+
+    DBus::Hihi hihi(msg);
+    hihi >> config_name >> number;
+
+    cout << "Method called with " << config_name << endl;
+
+    check_permission(conn, msg, config_name);
+    check_lock(conn, msg, config_name);
+
+    DBus::MessageMethodReturn reply(msg);
+
+    // TODO
+
+    DBus::Hoho hoho(reply);
+
+    conn.send(reply);
+
+    send_signal_snapshot_deleted(conn, config_name, number);
+}
+
+
+struct Comparing : public Job
+{
+    DBus::Connection* conn;
+    DBus::MessageMethodReturn* reply;
+
+    Comparison* comparison;
+
+    void done();
+
+protected:
+
+    virtual void operator()();
+
+};
+
+
+void
+reply_to_command_create_comparison(DBus::Connection& conn, DBus::Message& msg)
+{
+    cout << "command CreateComparison" << endl;
+
+    string config_name;
+    dbus_uint32_t number1, number2;
+
+    DBus::Hihi hihi(msg);
+    hihi >> config_name >> number1 >> number2;
+
+    cout << "Method called with " << config_name << " " << number1 << " " << number2 << endl;
+
+    check_permission(conn, msg, config_name);
+
+    Snapper* snapper = getSnapper(config_name);
+    Snapshots& snapshots = snapper->getSnapshots();
+    Snapshots::const_iterator snapshot1 = snapshots.find(number1);
+    Snapshots::const_iterator snapshot2 = snapshots.find(number2);
+
+    Comparison* comparison = new Comparison(snapper, snapshot1, snapshot2, true);
+    // TODO try, catch
+
+    Clients::iterator it = clients.find(msg.get_sender());
+    assert(it != clients.end());
+    it->comparisons.push_back(comparison);
+
+    Comparing* job = new Comparing;
+    job->conn = &conn;
+    job->reply = new DBus::MessageMethodReturn(msg);
+    job->comparison = comparison;
+
+    jobs.add(job);
+}
+
+
+void
+Comparing::operator()()
+{
+    boost::this_thread::sleep(seconds(2));
+
+    comparison->initialize();
+
+    boost::this_thread::sleep(seconds(2));
+}
+
+
+void
+Comparing::done()
+{
+    const Files& files = comparison->getFiles();
+    cout << comparison->getFiles().size() << endl;
+
+    for (Files::const_iterator it = files.begin(); it != files.end(); ++it)
+       cout << statusToString(it->getPreToPostStatus()) << " "
+            << it->getAbsolutePath(LOC_SYSTEM) << endl;
+
+    DBus::Hoho hoho(*reply);
+    hoho << (dbus_uint32_t) comparison->getFiles().size();
+    conn->send(*reply);
+
+    delete reply;
+}
+
+
+void
+reply_to_command_get_files(DBus::Connection& conn, DBus::Message& msg)
+{
+    cout << "command GetFiles" << endl;
+
+    string config_name;
+    dbus_uint32_t number1, number2;
+
+    DBus::Hihi hihi(msg);
+    hihi >> config_name >> number1 >> number2;
+
+    cout << "Method called with " << config_name << " " << number1 << " " << number2 << endl;
+
+    check_permission(conn, msg, config_name);
+
+    string sender = msg.get_sender();
+
+    DBus::MessageMethodReturn reply(msg);
+
+    Clients::iterator it = clients.find(sender);
+    assert(it != clients.end());
+
+    Comparison* comparison = it->find_comparison(config_name, number1, number2);
+
+    if (!comparison || !comparison->isInitialized())
+    {
+       // error
+    }
+
+    const Files& files = comparison->getFiles();
+
+    DBus::Hoho hoho(reply);
+    hoho << files;
+
+    conn.send(reply);
+}
+
+
+void
+reply_to_command_set_undo(DBus::Connection& conn, DBus::Message& msg)
+{
+    cout << "command SetUndo" << endl;
+
+    string config_name;
+    dbus_uint32_t number1, number2;
+    list<Undo> u;
+
+    DBus::Hihi hihi(msg);
+    hihi >> config_name >> number1 >> number2 >> u;
+
+    check_permission(conn, msg, config_name);
+
+    cout << "Method called with " << config_name << " " << number1 << " " << number2 << endl;
+
+    string sender = msg.get_sender();
+
+    DBus::MessageMethodReturn reply(msg);
+
+    Clients::iterator it = clients.find(sender);
+    assert(it != clients.end());
+
+    Comparison* comparison = it->find_comparison(config_name, number1, number2);
+
+    if (!comparison || !comparison->isInitialized())
+    {
+       // error
+    }
+
+    Files& files = comparison->getFiles();
+
+    for (list<Undo>::const_iterator it2 = u.begin(); it2 != u.end(); ++it2)
+    {
+       Files::iterator it3 = files.find(it2->filename);
+       if (it3 != files.end())
+           it3->setUndo(it2->undo);
+    }
+
+    conn.send(reply);
+}
+
+
+void
+reply_to_command_get_diff(DBus::Connection& conn, DBus::Message& msg)
+{
+    cout << "command GetDiff" << endl;
+
+    string config_name;
+    dbus_uint32_t number1, number2;
+    string filename;
+    string options;
+
+    DBus::Hihi hihi(msg);
+    hihi >> config_name >> number1 >> number2 >> filename >> options;
+
+    cout << "Method called with " << config_name << " " << number1 << " " << number2 << endl;
+
+    check_permission(conn, msg, config_name);
+
+    string sender = msg.get_sender();
+
+    Clients::iterator it = clients.find(sender);
+    assert(it != clients.end());
+
+    Comparison* comparison = it->find_comparison(config_name, number1, number2);
+
+    if (!comparison || !comparison->isInitialized())
+    {
+       // error
+    }
+
+    Files& files = comparison->getFiles();
+
+    Files::iterator it3 = files.find(filename);
+    assert(it3 != files.end());
+
+    vector<string> d = it3->getDiff(options);
+
+    DBus::MessageMethodReturn reply(msg);
+    DBus::Hoho hoho(reply);
+    hoho << d;
+
+    conn.send(reply);
+}
+
+
+void
+reply_to_command_debug(DBus::Connection& conn, DBus::Message& msg)
+{
+    cout << "command Debug" << endl;
+
+    // check_permission(conn, msg);
+
+    DBus::MessageMethodReturn reply(msg);
+
+    DBus::Hoho hoho(reply);
+
+    hoho.open_array("s");
+
+    hoho << "clients:";
+    for (list<Client>::iterator it = clients.begin(); it != clients.end(); ++it)
+    {
+       std::ostringstream s;
+       s << "    name:'" << it->name << "'";
+       if (it->name == msg.get_sender())
+           s << " myself";
+       s << " username:'" << conn.get_unix_username(msg) << "'";
+       if (!it->locks.empty())
+           s << " locks:'" << boost::join(it->locks, ",") << "'";
+       if (!it->comparisons.empty())
+           s << " comparisons:" << it->comparisons.size();
+       hoho << s.str();
+    }
+
+    hoho << "snappers:";
+    for (list<Snapper*>::iterator it = snappers.begin(); it != snappers.end(); ++it)
+    {
+       std::ostringstream s;
+       s << "    name:'" << (*it)->configName() << "'";
+       hoho << s.str();
+    }
+
+    if (!jobs.empty())
+    {
+       std::ostringstream s;
+       s << "running jobs:" << jobs.size();
+       hoho << s.str();
+    }
+
+    hoho.close_array();
+
+    conn.send(reply);
+}
+
+
+void
+client_connected(const string& name)
+{
+    clients.add(name);
+}
+
+
+void
+client_disconnected(const string& name)
+{
+    clients.remove(name);
+}
+
+
+void
+dispatch(DBus::Connection& conn, DBus::Message& msg)
+{
+    try
+    {
+       if (msg.is_method_call("org.opensuse.snapper", "Debug"))
+           reply_to_command_debug(conn, msg);
+       else if (msg.is_method_call("org.opensuse.snapper", "ListConfigs"))
+           reply_to_command_list_configs(conn, msg);
+       else if (msg.is_method_call("org.opensuse.snapper", "CreateConfig"))
+           reply_to_command_create_config(conn, msg);
+       else if (msg.is_method_call("org.opensuse.snapper", "LockConfig"))
+           reply_to_command_lock_config(conn, msg);
+       else if (msg.is_method_call("org.opensuse.snapper", "UnlockConfig"))
+           reply_to_command_unlock_config(conn, msg);
+       else if (msg.is_method_call("org.opensuse.snapper", "ListSnapshots"))
+           reply_to_command_list_snapshots(conn, msg);
+       else if (msg.is_method_call("org.opensuse.snapper", "CreateSnapshot"))
+           reply_to_command_create_snapshot(conn, msg);
+       else if (msg.is_method_call("org.opensuse.snapper", "DeleteSnapshot"))
+           reply_to_command_delete_snapshot(conn, msg);
+       else if (msg.is_method_call("org.opensuse.snapper", "CreateComparison"))
+           reply_to_command_create_comparison(conn, msg);
+       else if (msg.is_method_call("org.opensuse.snapper", "GetFiles"))
+           reply_to_command_get_files(conn, msg);
+       else if (msg.is_method_call("org.opensuse.snapper", "SetUndo"))
+           reply_to_command_set_undo(conn, msg);
+       else if (msg.is_method_call("org.opensuse.snapper", "GetDiff"))
+           reply_to_command_get_diff(conn, msg);
+    }
+    catch (DBus::MarshallingException)
+    {
+       DBus::MessageError reply(msg, "error.marshalling", DBUS_ERROR_FAILED);
+       conn.send(reply);
+    }
+    catch (Permissions)
+    {
+       DBus::MessageError reply(msg, "error.permissions", DBUS_ERROR_FAILED);
+       conn.send(reply);
+    }
+    catch (Lock)
+    {
+       DBus::MessageError reply(msg, "error.locked", DBUS_ERROR_FAILED);
+       conn.send(reply);
+    }
+}
+
+
+void
+listen(DBus::Connection& conn)
+{
+    y2mil("Listening for method calls and signals");
+
+    conn.request_name("org.opensuse.snapper", DBUS_NAME_FLAG_REPLACE_EXISTING);
+
+    conn.add_match("type='signal', interface='" DBUS_INTERFACE_DBUS "', member='NameOwnerChanged'");
+
+    int idle = 0;
+    while (++idle < 1000 || !clients.empty() || !jobs.empty())
+    {
+       conn.read_write(100);   // TODO
+
+       jobs.handle();
+
+       DBusMessage* tmp = dbus_connection_pop_message(conn.get_connection());
+       if (!tmp)
+           continue;
+
+       DBus::Message msg(tmp);
+
+       switch (msg.get_type())
+       {
+           case DBUS_MESSAGE_TYPE_METHOD_CALL:
+           {
+               y2mil("method call sender:'" << msg.get_sender() << "' path:'" <<
+                     msg.get_path() << "' interface:'" << msg.get_interface() <<
+                     "' member:'" << msg.get_member() << "'");
+
+               if (msg.is_method_call(DBUS_INTERFACE_INTROSPECTABLE, "Introspect"))
+               {
+                   reply_to_introspect(conn, msg);
+                   break;
+               }
+
+               if (clients.find(msg.get_sender()) == clients.end())
+               {
+                   y2mil("client connected invisible '" << msg.get_sender() << "'");
+                   client_connected(msg.get_sender());
+               }
+
+               dispatch(conn, msg);
+           }
+           break;
+
+           case DBUS_MESSAGE_TYPE_SIGNAL:
+           {
+               y2mil("signal sender:'" << msg.get_sender() << "' path:'" <<
+                     msg.get_path() << "' interface:'" << msg.get_interface() <<
+                     "' member:'" << msg.get_member() << "'");
+
+               if (msg.is_signal(DBUS_INTERFACE_DBUS, "NameOwnerChanged"))
+               {
+                   string name, old_owner, new_owner;
+
+                   DBus::Hihi hihi(msg);
+                   hihi >> name >> old_owner >> new_owner;
+
+                   if (name == new_owner && old_owner.empty())
+                   {
+                       y2mil("client connected '" << name << "'");
+                       client_connected(name);
+                   }
+
+                   if (name == old_owner && new_owner.empty())
+                   {
+                       y2mil("client disconnected '" << name << "'");
+                       client_disconnected(name);
+                   }
+               }
+           }
+           break;
+       }
+
+       idle = 0;
+    }
+
+    y2mil("Idle - exiting");
+}
+
+
+int
+main(int argc, char** argv)
+{
+    initDefaultLogger();
+
+    DBus::Connection conn(DBUS_BUS_SYSTEM);
+
+    listen(conn);
+
+    return 0;
+}
index c6a46f0dbae9131c194cdc5ebfe448d9d2f9cae4..f847beb8ce417a532ae5dded0c7b945c6343bec6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 Novell, Inc.
+ * Copyright (c) [2011-2012] Novell, Inc.
  *
  * All Rights Reserved.
  *
@@ -31,23 +31,63 @@ namespace snapper
 {
 
     Comparison::Comparison(const Snapper* snapper, Snapshots::const_iterator snapshot1,
-                          Snapshots::const_iterator snapshot2)
-       : snapper(snapper), snapshot1(snapshot1), snapshot2(snapshot2), files(this)
+                          Snapshots::const_iterator snapshot2, bool delay)
+       : snapper(snapper), snapshot1(snapshot1), snapshot2(snapshot2), initialized(false),
+         files(this)
     {
        if (snapshot1 == snapper->getSnapshots().end() ||
            snapshot2 == snapper->getSnapshots().end() ||
            snapshot1 == snapshot2)
            throw IllegalSnapshotException();
 
-       y2mil("num1:" << snapshot1->getNum() << " num2:" << snapshot2->getNum());
+       y2mil("num1:" << snapshot1->getNum() << " num2:" << snapshot2->getNum() << " delay:" <<
+             delay);
 
-       files.initialize();
+       if (!delay)
+       {
+           files.initialize();
+           initialized = true;
+       }
+    }
+
+
+    void
+    Comparison::initialize()
+    {
+       if (!initialized)
+       {
+           files.initialize();
+           initialized = true;
+       }
+    }
+
+
+    Files&
+    Comparison::getFiles()
+    {
+       if (!initialized)
+           throw;
+
+       return files;
+    }
+
+
+    const Files&
+    Comparison::getFiles() const
+    {
+       if (!initialized)
+           throw;
+
+       return files;
     }
 
 
     UndoStatistic
     Comparison::getUndoStatistic() const
     {
+       if (!initialized)
+           throw;
+
        return files.getUndoStatistic();
     }
 
@@ -55,6 +95,9 @@ namespace snapper
     bool
     Comparison::doUndo()
     {
+       if (!initialized)
+           throw;
+
        return files.doUndo();
     }
 
index e4ac8cd67302e007a945c768fdbc83d59f12d72e..474be89dddfa00a2f7c594a1572cd63d7389c2a5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 Novell, Inc.
+ * Copyright (c) [2011-2012] Novell, Inc.
  *
  * All Rights Reserved.
  *
@@ -37,15 +37,18 @@ namespace snapper
     public:
 
        Comparison(const Snapper* snapper, Snapshots::const_iterator snapshot1,
-                  Snapshots::const_iterator snapshot2);
+                  Snapshots::const_iterator snapshot2, bool delay = false);
 
        const Snapper* getSnapper() const { return snapper; }
 
        Snapshots::const_iterator getSnapshot1() const { return snapshot1; }
        Snapshots::const_iterator getSnapshot2() const { return snapshot2; }
 
-       Files& getFiles() { return files; }
-       const Files& getFiles() const { return files; }
+       void initialize();
+       bool isInitialized() const { return initialized; }
+
+       Files& getFiles();
+       const Files& getFiles() const;
 
        UndoStatistic getUndoStatistic() const;
 
@@ -58,6 +61,8 @@ namespace snapper
        Snapshots::const_iterator snapshot1;
        Snapshots::const_iterator snapshot2;
 
+       bool initialized;
+
        Files files;
 
     };
index f6fd7a367320b40a20e817c984055802ddd58777..7c19232befed21901b9cd54b2bd117f858e84cb4 100644 (file)
@@ -2,7 +2,10 @@
 # Makefile.am for snapper/snapper
 #
 
-AM_CXXFLAGS = -D_FILE_OFFSET_BITS=64 -I/usr/include/libxml2
+AM_CXXFLAGS = -D_FILE_OFFSET_BITS=64
+
+INCLUDES = -I/usr/include/libxml2
+
 
 lib_LTLIBRARIES = libsnapper.la
 
index d8ab010015f9497fd6a073dafbe903810ec429b5..5a9a9c19d0cc130a33d0d16787cab4cac6e84896 100644 (file)
@@ -535,7 +535,11 @@ namespace snapper
 
                    string subvolume = "/";
                    config.getValue("SUBVOLUME", subvolume);
-                   config_infos.push_back(ConfigInfo(*it, subvolume));
+
+                   vector<string> users;
+                   config.getValue("USERS", users);
+
+                   config_infos.push_back(ConfigInfo(*it, subvolume, users));
                }
                catch (const FileNotFoundException& e)
                {
index f640f679ccd6192b1b70ec541d0c0a171bd49590..fe47f6f442cb7e78999e1a405eec28a1ca186fa9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 Novell, Inc.
+ * Copyright (c) [2011-2012] Novell, Inc.
  *
  * All Rights Reserved.
  *
@@ -25,6 +25,7 @@
 
 
 #include <vector>
+#include <boost/noncopyable.hpp>
 
 #include "snapper/Snapshot.h"
 
@@ -68,10 +69,12 @@ namespace snapper
 
     struct ConfigInfo
     {
-       ConfigInfo(const string& config_name, const string& subvolume)
-           : config_name(config_name), subvolume(subvolume) {}
+       ConfigInfo(const string& config_name, const string& subvolume,
+                  const vector<string>& users)
+           : config_name(config_name), subvolume(subvolume), users(users) {}
        string config_name;
        string subvolume;
+       vector<string> users;
     };
 
 
@@ -115,7 +118,7 @@ namespace snapper
     };
 
 
-    class Snapper
+    class Snapper : boost::noncopyable
     {
     public: