diff --git a/LICENSE b/COPYING
similarity index 98%
rename from LICENSE
rename to COPYING
index 22fbe5dbacbe58748f688cac277d917aebb467b4..d159169d1050894d3ea3b98e1c965c4058208fe1 100644
--- a/LICENSE
+++ b/COPYING
@@ -1,7 +1,7 @@
-GNU GENERAL PUBLIC LICENSE
+                    GNU GENERAL PUBLIC LICENSE
                        Version 2, June 1991
 
- Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
@@ -290,8 +290,8 @@ to attach them to the start of each source file to most effectively
 convey the exclusion of warranty; and each file should have at least
 the "copyright" line and a pointer to where the full notice is found.
 
-    {description}
-    Copyright (C) {year}  {fullname}
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -329,11 +329,11 @@ necessary.  Here is a sample; alter the names:
   Yoyodyne, Inc., hereby disclaims all copyright interest in the program
   `Gnomovision' (which makes passes at compilers) written by James Hacker.
 
-  {signature of Ty Coon}, 1 April 1989
+  <signature of Ty Coon>, 1 April 1989
   Ty Coon, President of Vice
 
 This General Public License does not permit incorporating your program into
 proprietary programs.  If your program is a subroutine library, you may
 consider it more useful to permit linking proprietary applications with the
 library.  If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.
\ No newline at end of file
+Public License instead of this License.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..1ab418969d5330f5eae6e31bd4a4baf6546522b3
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,187 @@
+PKGNAME = dnfdaemon
+DATADIR=/usr/share
+SYSCONFDIR=/etc
+PKGDIR = $(DATADIR)/$(PKGNAME)
+PKGDIR_DNF = $(DATADIR)/$(PKGNAME_DNF)
+ORG_NAME = org.baseurl.DnfSystem
+ORG_RO_NAME = org.baseurl.DnfSession
+SUBDIRS = client/dnfdaemon
+VERSION=$(shell awk '/Version:/ { print $$2 }' ${PKGNAME}.spec)
+PYTHON=python
+GITDATE=git$(shell date +%Y%m%d)
+VER_REGEX=\(^Version:\s*[0-9]*\.[0-9]*\.\)\(.*\)
+BUMPED_MINOR=${shell VN=`cat ${PKGNAME}.spec | grep Version| sed  's/${VER_REGEX}/\2/'`; echo $$(($$VN + 1))}
+NEW_VER=${shell cat ${PKGNAME}.spec | grep Version| sed  's/\(^Version:\s*\)\([0-9]*\.[0-9]*\.\)\(.*\)/\2${BUMPED_MINOR}/'}
+NEW_REL=0.1.${GITDATE}
+DIST=${shell rpm --eval "%{dist}"}
+
+all: subdirs
+	
+subdirs:
+	for d in $(SUBDIRS); do make -C $$d; [ $$? = 0 ] || exit 1 ; done
+
+clean:
+	@rm -fv *~ *.tar.gz *.list *.lang 
+	for d in $(SUBDIRS); do make -C $$d clean ; done
+
+install:
+	mkdir -p $(DESTDIR)$(DATADIR)/dbus-1/system-services
+	mkdir -p $(DESTDIR)$(DATADIR)/dbus-1/services
+	mkdir -p $(DESTDIR)$(SYSCONFDIR)/dbus-1/system.d
+	mkdir -p $(DESTDIR)$(DATADIR)/polkit-1/actions
+	mkdir -p $(DESTDIR)$(PKGDIR)
+	install -m644 dbus/$(ORG_NAME).service $(DESTDIR)$(DATADIR)/dbus-1/system-services/.				
+	install -m644 dbus/$(ORG_RO_NAME).service $(DESTDIR)$(DATADIR)/dbus-1/services/.				
+	install -m644 dbus/$(ORG_NAME).conf $(DESTDIR)$(SYSCONFDIR)/dbus-1/system.d/.				
+	install -m644 policykit1/$(ORG_NAME).policy $(DESTDIR)$(DATADIR)/polkit-1/actions/.				
+	install -m755 dnfdaemon/dnfdaemon-system.py $(DESTDIR)/$(PKGDIR_DNF)/dnfdaemon-system
+	install -m755 dnfdaemon/dnfdaemon-session.py $(DESTDIR)/$(PKGDIR_DNF)/dnfdaemon-session
+	install -m644 dnfdaemon/common.py $(DESTDIR)/$(PKGDIR_DNF)/.
+	for d in $(SUBDIRS); do make DESTDIR=$(DESTDIR) -C $$d install; [ $$? = 0 ] || exit 1; done
+
+uninstall:
+	rm -f $(DESTDIR)$(DATADIR)/dbus-1/system-services/$(ORG_NAME).*
+	rm -f $(DESTDIR)$(DATADIR)/dbus-1/services/$(ORG_RO_NAME).*
+	rm -f $(DESTDIR)$(SYSCONFDIR)/dbus-1/system.d/$(ORG_NAME).*				
+	rm -r $(DESTDIR)$(DATADIR)/polkit-1/actions/$(ORG_NAME).*		
+	rm -rf $(DESTDIR)/$(PKGDIR)/
+
+selinux:
+	@$(MAKE) install
+	semanage fcontext -a -t rpm_exec_t $(DESTDIR)/$(PKGDIR)/dnfdaemon-system
+	restorecon $(DESTDIR)/$(PKGDIR_DNF)/dnfdaemon-system
+	
+
+# Run as root or you will get a password prompt for each test method :)
+test-verbose: FORCE
+	@nosetests -v -s test/
+
+
+# Run as root or you will get a password prompt for each test method :)
+test: FORCE
+	@nosetests -v test/
+
+# Run as root or you will get a password prompt for each test method :)
+test-system: FORCE
+	@nosetests -v test/test-system-api.py
+
+# Run as root or you will get a password prompt for each test method :)
+test-session: FORCE
+	@nosetests -v test/test-session-api.py
+
+
+# Run as root or you will get a password prompt for each test method :)
+test-devel: FORCE
+	@nosetests -v -s test/unit-devel.py
+
+instdeps:
+	sudo yum install python-nose python3-gobject pygobject3	
+
+get-builddeps:
+	yum install perl-TimeDate gettext intltool rpmdevtools python-devel python3-devel
+
+archive:
+	@rm -rf ${PKGNAME}-${VERSION}.tar.gz
+	@git archive --format=tar --prefix=$(PKGNAME)-$(VERSION)/ HEAD | gzip -9v >${PKGNAME}-$(VERSION).tar.gz
+	@cp ${PKGNAME}-$(VERSION).tar.gz $(shell rpm -E '%_sourcedir')
+	@rm -rf ${PKGNAME}-${VERSION}.tar.gz
+	@echo "The archive is in ${PKGNAME}-$(VERSION).tar.gz"
+	
+# needs perl-TimeDate for git2cl
+changelog:
+	@git log --pretty --numstat --summary --after=2008-10-22 | tools/git2cl > ChangeLog
+	
+upload: FORCE
+	@scp ~/rpmbuild/SOURCES/${PKGNAME}-${VERSION}.tar.gz fedorahosted.org:yumex
+	
+release:
+	@git commit -a -m "bumped version to $(VERSION)"
+	@git push
+	@git tag -f -m "Added ${PKGNAME}-${VERSION} release tag" ${PKGNAME}-${VERSION}
+	@git push --tags origin
+	@$(MAKE) archive
+	@$(MAKE) upload
+
+test-cleanup:	
+	@rm -rf ${PKGNAME}-${VERSION}.test.tar.gz
+	@echo "Cleanup the git release-test local branch"
+	@git checkout -f
+	@git checkout master
+	@git branch -D release-test
+
+show-vars:
+	@echo ${GITDATE}
+	@echo ${BUMPED_MINOR}
+	@echo ${NEW_VER}-${NEW_REL}
+	
+test-release:
+	@git checkout -b release-test
+	# +1 Minors version and add 0.1-gitYYYYMMDD release
+	@cat ${PKGNAME}.spec | sed  -e 's/${VER_REGEX}/\1${BUMPED_MINOR}/' -e 's/\(^Release:\s*\)\([0-9]*\)\(.*\)./\10.1.${GITDATE}%{?dist}/' > ${PKGNAME}-test.spec ; mv ${PKGNAME}-test.spec ${PKGNAME}.spec
+	@git commit -a -m "bumped ${PKGNAME} version ${NEW_VER}-${NEW_REL}"
+	# Make Changelog
+	@git log --pretty --numstat --summary | ./tools/git2cl > ChangeLog
+	@git commit -a -m "updated ChangeLog"
+		# Make archive
+	@rm -rf ${PKGNAME}-${NEW_VER}.tar.gz
+	@git archive --format=tar --prefix=$(PKGNAME)-$(NEW_VER)/ HEAD | gzip -9v >${PKGNAME}-$(NEW_VER).tar.gz
+	# Build RPMS
+	@rpmbuild -ta ${PKGNAME}-${NEW_VER}.tar.gz
+	@$(MAKE) test-cleanup
+
+test-inst:
+	@$(MAKE) test-release
+	sudo yum install ~/rpmbuild/RPMS/noarch/*${PKGNAME}-${NEW_VER}*.rpm
+
+test-reinst:
+	@$(MAKE) test-release
+	sudo yum reinstall ~/rpmbuild/RPMS/noarch/*${PKGNAME}-${NEW_VER}*.rpm
+	
+rpm:
+	@$(MAKE) archive
+	@rpmbuild -ba ${PKGNAME}.spec
+	
+test-builds:
+	@$(MAKE) test-release
+	@ssh timlau.fedorapeople.org rm -f public_html/files/${PKGNAME}/*
+	@scp ${PKGNAME}-${NEW_VER}.tar.gz timlau.fedorapeople.org:public_html/files/${PKGNAME}/${PKGNAME}-${NEW_VER}-${GITDATE}.tar.gz
+	@scp ~/rpmbuild/RPMS/noarch/${PKGNAME}-${NEW_VER}*.rpm timlau.fedorapeople.org:public_html/files/${PKGNAME}/.
+	@scp ~/rpmbuild/RPMS/noarch/python-${PKGNAME}-${NEW_VER}*.rpm timlau.fedorapeople.org:public_html/files/${PKGNAME}/.
+	@scp ~/rpmbuild/RPMS/noarch/python3-${PKGNAME}-${NEW_VER}*.rpm timlau.fedorapeople.org:public_html/files/${PKGNAME}/.
+	@scp ~/rpmbuild/SRPMS/${PKGNAME}-${NEW_VER}*.rpm timlau.fedorapeople.org:public_html/files/${PKGNAME}/.
+	
+review:	
+	@ssh timlau.fedorapeople.org rm -f public_html/files/${PKGNAME}/*
+	@scp ~/rpmbuild/SRPMS/${PKGNAME}-${VERSION}*.src.rpm  timlau.fedorapeople.org:public_html/files/${PKGNAME}/.
+	@scp ${PKGNAME}.spec timlau.fedorapeople.org:public_html/files/${PKGNAME}/.
+
+
+exit-session:
+	@/usr/bin/dbus-send --session --print-reply --dest="org.baseurl.DnfSession" / org.baseurl.DnfSession.Exit
+
+exit-system:
+	@sudo /usr/bin/dbus-send --system --print-reply --dest="org.baseurl.DnfSystem" / org.baseurl.DnfSystem.Exit
+	
+exit-both:
+	@/usr/bin/dbus-send --session --print-reply --dest="org.baseurl.DnfSession" / org.baseurl.DnfSession.Exit
+	@sudo /usr/bin/dbus-send --system --print-reply --dest="org.baseurl.DnfSystem" / org.baseurl.DnfSystem.Exit
+	
+start-session:
+	dnfdaemon/dnfdaemon-session.py -d -v --notimeout
+
+kill-both:
+	@-sudo killall -9 -r "dnfdaemon-system\.py" &> /dev/null 
+	@-sudo killall -9 -r "dnfdaemon-session\.py" &> /dev/null 
+	
+
+start-system:
+	sudo dnfdaemon/dnfdaemon-system.py -d -v --notimeout
+
+monitor-session:
+	dbus-monitor "type='signal',sender='org.baseurl.DnfSession',interface='org.baseurl.DnfSession'"	
+
+monitor-system:
+	dbus-monitor "type='signal',sender='org.baseurl.DnfSystem',interface='org.baseurl.DnfSystem'"	
+
+FORCE:
+	
diff --git a/README.md b/README.md
index 6fb14cc608fc52827f42ecbc0548e1872ccc2c56..05e1f59c39408b8fc0147ff132242b84de23a200 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,101 @@
 dnf-daemon
-==========
+===========
+
+dnf-daemon is a 2 DBus services there make part for dnf's API available for application via DBus calls.
+
+There is a DBus session bus service runnning as current user for performing readonly actions.
+
+There is a DBus system bus service runnning as root for performing actions there is making changes to the system
+
+This make it easy to do packaging action from your application no matter what language it is written in, as long as there
+is DBus binding for it.
+
+dnf-daemon uses PolicyKit for authentication for the system service, so when you call one of the commands (as normal users) you will get a  
+PolicyKit dialog to ask for password of a priviledged user like root.
+
+**dnf-daemon is still under heavy development and the API is not stable or complete yet**
+
+Source overview
+----------------
+
+    dnfdaemon/      Contains the daemon python source
+    client/         Contains the client API bindings for python 2.x & 3.x
+    test/           Unit test for the daemon and python bindings
+    dbus/           DBus system service setup files
+    policykit1/     PolicyKit authentication setup files
+
+
+
+How to install services and python bindings:
+-----------------------------------------------
+
+Run the following
+
+```
+	git clone ...
+	cd dnf-daemon
+	make test-inst
+```
+
+
+How to test:
+-------------
+
+just run:
+   
+    make test-verbose
+
+to run the unit test with output to console
+
+or this to just run the unit tests.
+
+    make test
+   
+To make the daemons shutdown
+-------------------------------
+
+Session:
+	
+	make exit-session
+	
+System
+	
+	make exit-system
+	
+Both
+
+    make exit-both
+   
+
+to run the daemons in debug mode from checkout:
+------------------------------------------------
+
+session (readonly as current user)
+
+	make run-session
+
+system (as root)
+	
+	make run-system
+
+
+API Definitions: 
+====================================
+
+The dnfdaemon api is documented [here](http://timlau.fedorapeople.org/dnfdaemon)
+
+The API is under development, so it might change, when we hit version 1.0, API methods will be frozen and
+API method names, parameters and return types will not change in future releases, new API can be added,
+but the old ones stays as is
+
+
+
+API Addition Checklist: 
+====================================
+* Add the new API methods to dnfdaemon-system.py and optional dnfdaemon-session.py
+* Add client api method in DnfDaemonBase if it is available in both daemon
+  or in DnfDaemonClient is it is a system only api.
+* Add unit tests for the api in test/test-system-api.py and optional to test/test-system-api.py if it exists in the session api
+* Update docs/server.rst and docs/client-python.api ( add new api method to members )
+* All unit tests must pass (make test) before pushing to github
 
-DBus daemon for doing package action with the dnf package manager
diff --git a/client/dnfdaemon/Makefile b/client/dnfdaemon/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..2e6b51b0a018a99c02fdc5fa650a3646697e87c9
--- /dev/null
+++ b/client/dnfdaemon/Makefile
@@ -0,0 +1,27 @@
+PYTHON=python
+PYTHON3=python3
+PACKAGE = dnfdaemon
+PYFILES = $(wildcard *.py)
+PYVER := $(shell $(PYTHON) -c 'import sys; print("%.3s" %(sys.version))')
+PYSYSDIR := $(shell $(PYTHON) -c 'import sys; print(sys.prefix)')
+PYLIBDIR = $(PYSYSDIR)/lib/python$(PYVER)
+PKGDIR = $(PYLIBDIR)/site-packages/$(PACKAGE)
+PYVER3 := $(shell $(PYTHON3) -c 'import sys; print("%.3s" %(sys.version))')
+PYSYSDIR3 := $(shell $(PYTHON3) -c 'import sys; print(sys.prefix)')
+PYLIBDIR3 = $(PYSYSDIR3)/lib/python$(PYVER3)
+PKGDIR3 = $(PYLIBDIR3)/site-packages/$(PACKAGE)
+
+all: 
+	echo "Nothing to do"
+
+clean:
+	rm -rf *.pyc *.pyo *~ __pycache__/
+	
+
+install:
+	mkdir -p $(DESTDIR)/$(PKGDIR)
+	mkdir -p $(DESTDIR)/$(PKGDIR3)
+	for p in $(PYFILES) ; do \
+		install -m 644 $$p $(DESTDIR)/$(PKGDIR)/$$p; \
+		install -m 644 $$p $(DESTDIR)/$(PKGDIR3)/$$p; \
+	done
diff --git a/client/dnfdaemon/__init__.py b/client/dnfdaemon/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b529469d1375f446dfc3f65d513873cc637d1962
--- /dev/null
+++ b/client/dnfdaemon/__init__.py
@@ -0,0 +1,728 @@
+# coding: utf-8
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+# (C) 2013 - Tim Lauridsen <timlau@fedoraproject.org>
+
+"""
+This is a Python 2.x & 3.x client API for the dnf-daemon Dbus Service
+
+This module gives a simple pythonic interface to doing Yum package action using the
+yum-daemon Dbus service.
+
+It use async call to the dnf-daemon, so signal can be catched and a Gtk gui dont get unresonsive
+
+There is 2 classes :class:`DnfDaemonClient` & :class:`DnfDaemonReadOnlyClient`
+
+:class:`DnfDaemonClient` uses a system DBus service running as root and can make chages to the system.
+
+:class:`DnfDaemonReadOnlyClient` uses a session DBus service running as current user and can only do readonly
+actions.
+
+Usage: (Make your own subclass based on :class:`dnfdaemon.DnfDaemonClient` and overload the signal handlers)::
+
+
+    from dnfdaemon import DnfDaemonClient
+
+    class MyClient(DnfDaemonClient):
+
+        def __init(self):
+            DnfDaemonClient.__init__(self)
+            # Do your stuff here
+
+        def on_UpdateProgress(self,name,frac,fread,ftime):
+            # Do your stuff here
+            pass
+
+        def on_TransactionEvent(self,event, data):
+            # Do your stuff here
+            pass
+
+        def on_RPMProgress(self, package, action, te_current, te_total, ts_current, ts_total):
+            # Do your stuff here
+            pass
+
+        def on_GPGImport(self, pkg_id, userid, hexkeyid, keyurl,  timestamp ):
+           # do stuff here
+           pass
+
+
+Usage: (Make your own subclass based on :class:`dnfdaemon.DnfDaemonReadOnlyClient` and overload the signal handlers)::
+
+
+    from dnfdaemon import DnfDaemonReadOnlyClient
+
+    class MyClient(DnfDaemonReadOnlyClient):
+
+        def __init(self):
+            DnfDaemonClient.__init__(self)
+            # Do your stuff here
+
+        def on_UpdateProgress(self,name,frac,fread,ftime):
+            # Do your stuff here
+            pass
+
+"""
+
+import json
+import sys
+import re
+import weakref
+import logging
+
+logger = logging.getLogger("dnfdaemon.client")
+
+from gi.repository import Gio, GObject
+
+ORG = 'org.baseurl.DnfSystem'
+INTERFACE = ORG
+
+ORG_READONLY = 'org.baseurl.DnfSession'
+INTERFACE_READONLY = ORG_READONLY
+
+DBUS_ERR_RE = re.compile('^GDBus.Error:([\w\.]*): (.*)$')
+
+###############################################################################
+# Exceptions
+###############################################################################
+
+
+class DaemonError(Exception):
+    'Error from the backend'
+
+class AccessDeniedError(DaemonError):
+    'User press cancel button in policykit window'
+
+class LockedError(DaemonError):
+    'The Yum daemon is locked'
+
+class TransactionError(DaemonError):
+    'The yum transaction failed'
+
+###############################################################################
+# Helper Classes
+###############################################################################
+
+
+class DBus:
+    '''
+    Helper class to work with GDBus in a easier way
+    '''
+    def __init__(self, conn):
+        self.conn = conn
+
+    def get(self, bus, obj, iface=None):
+        if iface is None:
+            iface = bus
+        return Gio.DBusProxy.new_sync(
+            self.conn, 0, None, bus, obj, iface, None
+        )
+
+    def get_async(self, callback, bus, obj, iface=None):
+        if iface is None:
+            iface = bus
+        Gio.DBusProxy.new(
+            self.conn, 0, None, bus, obj, iface, None, callback, None
+        )
+
+class WeakMethod:
+    '''
+    helper class to work with a weakref class method
+    '''
+    def __init__(self, inst, method):
+        self.proxy = weakref.proxy(inst)
+        self.method = method
+
+    def __call__(self, *args):
+        return getattr(self.proxy, self.method)(*args)
+
+
+# Get the system bus
+system = DBus(Gio.bus_get_sync(Gio.BusType.SYSTEM, None))
+session = DBus(Gio.bus_get_sync(Gio.BusType.SESSION, None))
+
+###############################################################################
+# Main Client Class
+###############################################################################
+class DnfDaemonBase:
+    def __init__(self, bus, org, interface):
+        self.bus = bus
+        self.dbus_org = org
+        self.dbus_interface = interface
+        self.daemon = self._get_daemon(bus, org, interface)
+        logger.debug("%s daemon loaded - version :  %s" % (interface,self.daemon.GetVersion()))
+
+    def _get_daemon(self,bus, org, interface):
+        ''' Get the daemon dbus proxy object'''
+        try:
+            proxy = bus.get( org, "/", interface)
+            proxy.GetVersion() # Get daemon version, to check if it is alive
+            proxy.connect('g-signal', WeakMethod(self, '_on_g_signal')) # Connect the Dbus signal handler
+            return proxy
+        except Exception as err:
+            self._handle_dbus_error(err)
+
+    def _on_g_signal(self, proxy, sender, signal, params):
+        '''
+        DBUS signal Handler
+        :param proxy: DBus proxy
+        :param sender: DBus Sender
+        :param signal: DBus signal
+        :param params: DBus signal parameters
+        '''
+        args = params.unpack() # unpack the glib variant
+        self.handle_dbus_signals(proxy, sender, signal, args)
+
+    def handle_dbus_signals(self, proxy, sender, signal, args):
+        """
+        Overload in child class
+        """
+        pass
+
+    def _handle_dbus_error(self, err):
+        '''
+        Parse error from service and raise python Exceptions
+        :param err:
+        :type err:
+        '''
+        exc, msg = self._parse_error()
+        if exc != "":
+            logger.error("Exception   : %s",exc)
+            logger.error("   message  : %s",msg)
+        if exc == self.dbus_org+'.AccessDeniedError':
+            raise AccessDeniedError(msg)
+        elif exc == self.dbus_org+'.LockedError':
+            raise LockedError(msg)
+        elif exc == self.dbus_org+'.TransactionError':
+            raise TransactionError(msg)
+        elif exc == self.dbus_org+'.NotImplementedError':
+            raise TransactionError(msg)
+        else:
+            raise DaemonError(str(err))
+
+    def _parse_error(self):
+        '''
+        parse values from a DBus releated exception
+        '''
+        (type, value, traceback) = sys.exc_info()
+        res = DBUS_ERR_RE.match(str(value))
+        if res:
+            return res.groups()
+        return "",""
+
+    def _return_handler(self, obj, result, user_data):
+        '''
+        Async DBus call, return handler
+        :param obj:
+        :type obj:
+        :param result:
+        :type result:
+        :param user_data:
+        :type user_data:
+        '''
+        if isinstance(result, Exception):
+            #print(result)
+            user_data['result'] = None
+            user_data['error'] = result
+        else:
+            user_data['result'] = result
+            user_data['error'] = None
+        user_data['main_loop'].quit()
+
+    def _get_result(self, user_data):
+        '''
+        Get return data from async call or handle error
+        :param user_data:
+        :type user_data:
+        '''
+        if user_data['error']: # Errors
+            self._handle_dbus_error(user_data['error'])
+        else:
+            return user_data['result']
+
+    def _run_dbus_async(self, cmd, *args):
+        '''
+        Make an async call to a DBus method in the yumdaemon service
+        :param cmd: method to run
+        :type cmd: string
+        '''
+        main_loop = GObject.MainLoop()
+        data = {'main_loop': main_loop}
+        func = getattr(self.daemon,cmd)
+        func(*args, result_handler=self._return_handler, user_data=data, timeout=GObject.G_MAXINT) # timeout = infinite
+        data['main_loop'].run()
+        result = self._get_result(data)
+        return result
+
+
+    def _run_dbus_sync(self, cmd, *args):
+        '''
+        Make a sync call to a DBus method in the yumdaemon service
+        :param cmd:
+        :type cmd:
+        '''
+        func = getattr(self.daemon,cmd)
+        return func(*args)
+
+###############################################################################
+# Dbus Signal Handlers (Overload in child class)
+###############################################################################
+
+    def on_UpdateProgress(self,name,frac,fread,ftime):
+        if name.startswith('repomd'):
+            print("repo metadata : %.2f" % frac)
+        elif "/" in name:
+            repo,file = name.split("/",1)
+            print("getting %s from %s repository : %.2f" % (file,repo,frac))
+        else:
+            print("downloading : %s %s" % (name,frac))
+
+    def on_TransactionEvent(self,event, data):
+        print("TransactionEvent : %s" % event)
+        if data:
+            print("Data :\n", data)
+
+    def on_RPMProgress(self, package, action, te_current, te_total, ts_current, ts_total):
+        print("RPMProgress : %s %s" % (action, package))
+
+    def on_GPGImport(self, pkg_id, userid, hexkeyid, keyurl,  timestamp ):
+        values =  (pkg_id, userid, hexkeyid, keyurl, timestamp)
+        print("on_GPGImport : %s" % (repr(values)))
+
+    def on_DownloadStart(self, num_files, num_bytes):
+        ''' Starting a new parallel download batch '''
+        values =  (num_files, num_bytes)
+        print("on_DownloadStart : %s" % (repr(values)))
+
+    def on_DownloadProgress(self, name, frac, total_frac, total_files):
+        ''' Progress for a single instance in the batch '''
+        values =  (name, frac, total_frac, total_files)
+        print("on_DownloadProgress : %s" % (repr(values)))
+
+    def on_DownloadEnd(self, name, status, msg):
+        ''' Download of af single instace ended '''
+        values =  (name, status, msg)
+        print("on_DownloadEnd : %s" % (repr(values)))
+
+    def on_RepoMetaDataProgress(self, name, frac):
+        ''' Repository Metadata Download progress '''
+        values =  (name, frac)
+        print("on_RepoMetaDataProgress : %s" % (repr(values)))
+
+###############################################################################
+# API Methods
+###############################################################################
+
+
+    def Lock(self):
+        '''
+        Get the yum lock, this give exclusive access to the daemon and yum
+        this must always be called before doing other actions
+        '''
+        try:
+            return self._run_dbus_async('Lock')
+        except Exception as err:
+            self._handle_dbus_error(err)
+
+    def Unlock(self):
+        '''
+        Release the yum lock
+        '''
+        try:
+            self.daemon.Unlock()
+        except Exception as err:
+            self._handle_dbus_error(err)
+
+    def SetWatchdogState(self,state):
+        '''
+        Set the Watchdog state
+
+        :param state: True = Watchdog active, False = Watchdog disabled
+        :type state: boolean (b)
+        '''
+        try:
+            self.daemon.SetWatchdogState("(b)",state)
+        except Exception as err:
+            self._handle_dbus_error(err)
+
+    def GetPackageWithAttributes(self, pkg_filter, fields):
+        '''
+        Get a list of pkg list for a given package filter
+        each pkg list contains [pkg_id, field,....] where field is a atrribute of the package object
+        Ex. summary, size etc.
+
+        :param pkg_filter: package filter ('installed','available','updates','obsoletes','recent','extras')
+        :type pkg_filter: string
+        :param fields: yum package objects attributes to get.
+        :type fields: list of strings
+        '''
+        result = self._run_dbus_async('GetPackageWithAttributes','(sas)',pkg_filter, fields)
+        return json.loads(result)
+
+
+    def GetRepositories(self, repo_filter):
+        '''
+        Get a list of repository ids where name matches a filter
+
+        :param repo_filter: filter to match
+        :return: list of repo id's
+        '''
+        result = self._run_dbus_async('GetRepositories','(s)',repo_filter)
+        return [str(r) for r in result]
+
+
+    def GetRepo(self, repo_id):
+        '''
+        Get a dictionary of information about a given repo id.
+
+        :param repo_id: repo id to get information from
+        :return: dictionary with repo info
+        '''
+        result = json.loads(self._run_dbus_async('GetRepo','(s)',repo_id))
+        return result
+
+    def SetEnabledRepos(self, repo_ids):
+        '''
+        Enabled a list of repositories, disabled all other repos
+
+        :param repo_ids: list of repo ids to enable
+        :param sender:
+        '''
+        self._run_dbus_async('SetEnabledRepos','(as)', repo_ids)
+
+
+    def GetConfig(self, setting):
+        '''
+        Read a config setting from yum.conf
+
+        :param setting: setting to read
+        :type setting: string
+        '''
+        result = json.loads(self._run_dbus_async('GetConfig','(s)',setting))
+        return result
+
+    def GetAttribute(self, pkg_id, attr):
+        '''
+        Get yum package attribute (description, filelist, changelog etc)
+
+        :param pkg_id: pkg_id to get attribute from
+        :param attr: name of attribute to get
+        '''
+        result = self._run_dbus_async('GetAttribute','(ss)',pkg_id, attr)
+        if result == ':none': # illegal attribute
+            result = None
+        elif result == ':not_found': # package not found
+            result = None # FIXME: maybe raise an exception
+        else:
+            result = json.loads(result)
+        return result
+
+    def GetUpdateInfo(self, pkg_id):
+        '''
+        Get Updateinfo for a package
+
+        :param pkg_id: pkg_id to get update info from
+        '''
+        result = self._run_dbus_async('GetUpdateInfo','(s)',pkg_id)
+        return json.loads(result)
+
+    def GetPackages(self, pkg_filter):
+        '''
+        Get a list of pkg ids for a given filter (installed, updates ..)
+
+        :param pkg_filter: package filter ('installed','available','updates','obsoletes','recent','extras')
+        :type pkg_filter: string
+        :return: list of pkg_id's
+        :rtype: list of strings
+        '''
+        return self._run_dbus_async('GetPackages','(s)',pkg_filter)
+
+
+    def GetPackagesByName(self, name, newest_only=True):
+        '''
+        Get a list of pkg ids for starts with name
+
+        :param name: name prefix to match
+        :type name: string
+        :param newest_only: show only the newest match or every match.
+        :type newest_only: boolean
+        :return: list of pkg_is's
+        '''
+        return self._run_dbus_async('GetPackagesByName','(sb)',name, newest_only)
+
+
+    def GetGroups(self):
+        '''
+        Get list of Groups
+        '''
+        return json.loads(self._run_dbus_async('GetGroups'))
+
+    def GetGroupPackages(self, grp_id, grp_flt):
+        '''
+        Get packages in a group
+
+        :param grp_id: the group id to get packages for
+        :param grp_flt: the filter ('all' = all packages ,'default' = packages to be installed, before the group is installed)
+        '''
+        return self._run_dbus_async('GetGroupPackages', '(ss)', grp_id, grp_flt)
+
+
+    def Search(self, fields, keys, match_all, newest_only, tags):
+        '''
+        Search for packages where keys is matched in fields
+
+        :param fields: yum po attributes to search in
+        :type fields: list of strings
+        :param keys: keys to search for
+        :type keys: list of strings
+        :param match_all: match all keys or only one
+        :type match_all: boolean
+        :param newest_only: return only the newest version of packages
+        :type newest_only: boolean
+        :param tags: search pkgtags
+        :type tags: boolean
+        :return: list of pkg_id's
+
+        '''
+        return self._run_dbus_async('Search','(asasbbb)',fields, keys, match_all, newest_only, tags)
+
+    def Exit(self):
+        '''
+        End the daemon
+        '''
+        self._run_dbus_async('Exit')
+
+###############################################################################
+# Helper methods
+###############################################################################
+
+    def to_pkg_tuple(self, id):
+        ''' split the pkg_id into a tuple'''
+        (n, e, v, r, a, repo_id)  = str(id).split(',')
+        return (n, e, v, r, a, repo_id)
+
+    def to_txmbr_tuple(self, id):
+        ''' split the txmbr_id into a tuple'''
+        (n, e, v, r, a, repo_id, ts_state)  = str(id).split(',')
+        return (n, e, v, r, a, repo_id, ts_state)
+
+
+
+class DnfDaemonReadOnlyClient(DnfDaemonBase):
+    '''
+    A class to communicate with the yumdaemon DBus services in a easy way
+    '''
+
+    def __init__(self):
+        DnfDaemonBase.__init__(self, session,ORG_READONLY,INTERFACE_READONLY)
+
+    def handle_dbus_signals(self, proxy, sender, signal, args):
+        '''
+        DBUS signal Handler
+        '''
+        if signal == "UpdateProgress":
+            self.on_UpdateProgress(*args)
+        else:
+            print("Unhandled Signal : "+signal," Param: ",args)
+
+
+class DnfDaemonClient(DnfDaemonBase):
+    '''
+    A class to communicate with the yumdaemon DBus services in a easy way
+    '''
+
+    def __init__(self):
+        DnfDaemonBase.__init__(self, system,ORG,INTERFACE)
+
+    def handle_dbus_signals(self, proxy, sender, signal, args):
+        '''
+        DBUS signal Handler
+        '''
+        if signal == "UpdateProgress":
+            self.on_UpdateProgress(*args)
+        elif signal == "TransactionEvent":
+            self.on_TransactionEvent(*args)
+        elif signal == "RPMProgress":
+            self.on_RPMProgress(*args)
+        elif signal == "GPGImport":
+            self.on_GPGImport(*args)
+        elif signal == "DownloadStart":
+            self.on_DownloadStart(*args)
+        elif signal == "DownloadEnd":
+            self.on_DownloadEnd(*args)
+        elif signal == "DownloadProgress":
+            self.on_DownloadProgress(*args)
+        elif signal == "RepoMetaDataProgress":
+            self.on_RepoMetaDataProgress(*args)
+        else:
+            print("Unhandled Signal : "+signal," Param: ",args)
+
+###############################################################################
+# API Methods
+###############################################################################
+
+    def SetConfig(self, setting, value):
+        '''
+        set a yum config setting
+
+        :param setting: yum conf setting to set
+        :param value: value to set
+        '''
+        result = self._run_dbus_async('SetConfig','(ss)',setting, json.dumps(value))
+        return result
+
+
+    def ClearTransaction(self):
+        '''
+        Clear the current transaction
+        '''
+        return self._run_dbus_async('ClearTransaction')
+
+
+
+    def GetTransaction(self):
+        '''
+        Get the current transaction
+
+        :return: the current transaction
+        '''
+        return self._run_dbus_async('GetTransaction')
+
+
+    def AddTransaction(self, id, action):
+        '''
+        Add an package to the current transaction
+
+        :param id: package id for the package to add
+        :type id: string
+        :param action: the action to perform ( install, update, remove, obsolete, reinstall, downgrade, localinstall )
+        :type action: string
+        '''
+        return self._run_dbus_async('AddTransaction','(ss)',id, action)
+
+
+    def Install(self, pattern):
+        '''
+        Do a install <pattern string>, same as yum install <pattern string>
+
+        :param pattern: package pattern to install
+        :type pattern: string
+       '''
+        return json.loads(self._run_dbus_async('Install','(s)',pattern))
+
+
+    def Remove(self, pattern):
+        '''
+        Do a install <pattern string>, same as yum remove <pattern string>
+
+        :param pattern: package pattern to remove
+        :type pattern: string
+        '''
+        return json.loads(self._run_dbus_async('Remove','(s)',pattern))
+
+
+    def Update(self, pattern):
+        '''
+        Do a update <pattern string>, same as yum update <pattern string>
+
+        :param pattern: package pattern to update
+        :type pattern: string
+
+        '''
+        return json.loads(self._run_dbus_async('Update','(s)',pattern))
+
+
+    def Reinstall(self, pattern):
+        '''
+        Do a reinstall <pattern string>, same as yum reinstall <pattern string>
+
+        :param pattern: package pattern to reinstall
+        :type pattern: string
+
+        '''
+        return json.loads(self._run_dbus_async('Reinstall','(s)',pattern))
+
+
+    def Downgrade(self, pattern):
+        '''
+        Do a install <pattern string>, same as yum remove <pattern string>
+
+        :param pattern: package pattern to downgrade
+        :type pattern: string
+        '''
+        return json.loads(self._run_dbus_async('Downgrade','(s)',pattern))
+
+
+
+    def BuildTransaction(self):
+        '''
+        Get a list of pkg ids for the current availabe updates
+        '''
+        return json.loads(self._run_dbus_async('BuildTransaction'))
+
+
+    def RunTransaction(self):
+        '''
+        Get a list of pkg ids for the current availabe updates
+        '''
+        return self._run_dbus_async('RunTransaction')
+
+
+    def GetHistoryByDays(self, start_days, end_days):
+        '''
+        Get History transaction in a interval of days from today
+
+        :param start_days: start of interval in days from now (0 = today)
+        :type start_days: integer
+        :param end_days:end of interval in days from now
+        :type end_days: integer
+        :return: a list of (transaction is, date-time) pairs
+        :type sender: json encoded string
+        '''
+        value = self._run_dbus_async('GetHistoryByDays','(ii)', start_days, end_days)
+        return json.loads(value)
+
+    def HistorySearch(self, pattern):
+        '''
+        Search the history for transaction matching a pattern
+
+        :param pattern: patterne to match
+        :type pattern: list (strings)
+        :return: list of (tid,isodates)
+        :type sender: json encoded string
+        '''
+        value = self._run_dbus_async('HistorySearch','(as)', pattern)
+        return json.loads(value)
+
+    def GetHistoryPackages(self, tid):
+        '''
+        Get packages from a given yum history transaction id
+
+        :param tid: history transaction id
+        :type tid: integer
+        :return: list of (pkg_id, state, installed) pairs
+        :rtype: list
+        '''
+        value = self._run_dbus_async('GetHistoryPackages','(i)',tid)
+        return json.loads(value)
+
+    def ConfirmGPGImport(self, hexkeyid, confirmed):
+        '''
+        Confirm import of at GPG Key by yum
+
+        :param hexkeyid: hex keyid for GPG key
+        :param confirmed: confirm import of key (True/False)
+        '''
+        self._run_dbus_async('ConfirmGPGImport','(si)',hexkeyid, confirmed)
+
diff --git a/dbus/org.baseurl.DnfSession.service b/dbus/org.baseurl.DnfSession.service
new file mode 100644
index 0000000000000000000000000000000000000000..b571126785ed4fa03392f5faa1bba10d2cf79ea5
--- /dev/null
+++ b/dbus/org.baseurl.DnfSession.service
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.baseurl.DnfSession
+Exec=/usr/share/dnfdaemon/yumdaemon-session
diff --git a/dbus/org.baseurl.DnfSystem.conf b/dbus/org.baseurl.DnfSystem.conf
new file mode 100644
index 0000000000000000000000000000000000000000..6608e9543e4c37f974919e04d725549d33c9b3e5
--- /dev/null
+++ b/dbus/org.baseurl.DnfSystem.conf
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+    <!-- Only root can become service owner -->
+    <policy user="root">
+        <allow own="org.baseurl.DnfSystem"/>
+        <allow send_destination="org.baseurl.DnfSystem"/>
+        <allow send_interface="org.baseurl.DnfSystem"/>
+    </policy>
+    
+    <!-- Anyone can invoke method -->
+    <policy context="default">
+        <allow send_destination="org.baseurl.DnfSystem"/>
+        <allow send_interface="org.baseurl.DnfSystem"/>
+    </policy>
+</busconfig>
diff --git a/dbus/org.baseurl.DnfSystem.service b/dbus/org.baseurl.DnfSystem.service
new file mode 100644
index 0000000000000000000000000000000000000000..da910db292ce57aca4260b49ea815fe642dd58f3
--- /dev/null
+++ b/dbus/org.baseurl.DnfSystem.service
@@ -0,0 +1,4 @@
+[D-BUS Service]
+Name=org.baseurl.DnfSystem
+Exec=/usr/share/dnfdaemon/dnfdaemon-system
+User=root
diff --git a/dnfdaemon.spec b/dnfdaemon.spec
new file mode 100644
index 0000000000000000000000000000000000000000..91d848971fd65c7b80c1fba29cea1e09726956ea
--- /dev/null
+++ b/dnfdaemon.spec
@@ -0,0 +1,87 @@
+%global dnf_org org.baseurl.Dnf
+
+Name:           dnfaemon
+Version:        0.1.0
+Release:        1%{?dist}
+Summary:        DBus daemon for yum package actions
+
+License:        GPLv2+
+URL:            https://github.com/timlau/dnf-daemon
+Source0:        https://fedorahosted.org/releases/y/u/yumex/%{name}-%{version}.tar.gz
+
+BuildArch:      noarch
+BuildRequires:  python2-devel
+Requires:       dbus-python
+Requires:       dnf >= 0.4.17
+Requires:       polkit
+
+Requires(post):     policycoreutils-python
+Requires(postun):   policycoreutils-python
+
+%description
+Dbus daemon for performing package actions with the dnf package manager
+
+%prep
+%setup -q
+
+
+%build
+# Nothing to build
+
+%install
+make install DESTDIR=$RPM_BUILD_ROOT DATADIR=%{_datadir} SYSCONFDIR=%{_sysconfdir}
+
+%package -n python3-%{name}
+Summary:        Python 3 api for communicating with the dnf-daemon DBus service
+Group:          Applications/System
+BuildRequires:  python3-devel
+Requires:       %{name} = %{version}-%{release}
+Requires:       python3-gobject
+
+%description -n python3-%{name}
+Python 3 api for communicating with the dnf-daemon DBus service
+
+
+%files -n  python3-%{name}
+%{python3_sitelib}/%{name}/
+
+%package -n python-%{name}
+Summary:        Python 2 api for communicating with the dnf-daemon DBus service
+Group:          Applications/System
+BuildRequires:  python2-devel
+Requires:       %{name} = %{version}-%{release}
+Requires:       pygobject3
+
+%description -n python-%{name}
+Python 2 api for communicating with the dnf-daemon DBus service
+
+
+%files -n  python-%{name}
+%{python_sitelib}/%{name}/
+
+# apply the right selinux file context
+# http://fedoraproject.org/wiki/PackagingDrafts/SELinux#File_contexts
+
+%post
+semanage fcontext -a -t rpm_exec_t '%{_datadir}/%{name}/%{name}-system' 2>/dev/null || :
+restorecon -R %{_datadir}/%{name}/%{name}-system || :
+
+%postun
+if [ $1 -eq 0 ] ; then  # final removal
+semanage fcontext -d -t rpm_exec_t '%{_datadir}/%{name}/%{name}-system' 2>/dev/null || :
+fi
+
+%files
+%doc README.md examples/ ChangeLog COPYING
+%{_datadir}/dbus-1/system-services/%{dnf_org}*
+%{_datadir}/dbus-1/services/%{dnf_org}*
+%{_datadir}/%{name}/
+%{_datadir}/polkit-1/actions/%{dnf_org}*
+# this should not be edited by the user, so no %%config
+%{_sysconfdir}/dbus-1/system.d/%{dnf_org}*
+
+
+%changelog
+
+* Sat Mar 08 2014 Tim Lauridsen <timlau@fedoraproject.org> 0.10-1
+- Initial rpm for dnfdaemon
diff --git a/dnfdaemon/common.py b/dnfdaemon/common.py
new file mode 100644
index 0000000000000000000000000000000000000000..35045c7b4908faadcb7a0ce17aeb3f128ccf564a
--- /dev/null
+++ b/dnfdaemon/common.py
@@ -0,0 +1,855 @@
+# -*- coding: utf-8 -*-
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+# (C) 2013 - Tim Lauridsen <timlau@fedoraproject.org>
+
+"""
+Common stuff for the dnfdaemon dbus services
+"""
+from __future__ import print_function
+from __future__ import absolute_import
+import dbus
+import dbus.service
+import dbus.glib
+import gobject
+import json
+import logging
+from datetime import datetime
+
+import sys
+from time import time
+
+import dnf
+import dnf.yum
+import dnf.const
+import dnf.conf
+import dnf.subject
+from dnf.callback import DownloadProgress, STATUS_OK
+import hawkey
+
+FAKE_ATTR = ['downgrades','action','pkgtags','changelog']
+NONE = json.dumps(None)
+
+
+#------------------------------------------------------------------------------ Callback handlers
+
+logger = logging.getLogger('dnfdaemon.service')
+
+def Logger(func):
+    """
+    This decorator catch yum exceptions and send fatal signal to frontend
+    """
+    def newFunc(*args, **kwargs):
+        logger.debug("%s started args: %s " % (func.__name__, repr(args[1:])))
+        rc = func(*args, **kwargs)
+        logger.debug("%s ended" % func.__name__)
+        return rc
+
+    newFunc.__name__ = func.__name__
+    newFunc.__doc__ = func.__doc__
+    newFunc.__dict__.update(func.__dict__)
+    return newFunc
+
+class DownloadCallback:
+    '''
+    Yum Download callback handler class
+    the updateProgress will be called while something is being downloaded
+    '''
+    def __init__(self):
+        pass
+
+    def updateProgress(self,name,frac,fread,ftime):
+        '''
+        Update the progressbar
+        :param name: filename
+        :param frac: Progress fracment (0 -> 1)
+        :param fread: formated string containing BytesRead
+        :param ftime : formated string containing remaining or elapsed time
+        '''
+        # send a DBus signal with progress info
+        self.UpdateProgress(name,frac,fread,ftime)
+
+# Parallel Download Progress
+    def downloadStart(self, num_files, num_bytes):
+        ''' Starting a new parallel download batch '''
+        self.DownloadStart(num_files, num_bytes) # send a signal
+
+    def downloadProgress(self, name, frac, total_frac, total_files):
+        ''' Progress for a single instance in the batch '''
+        self.DownloadProgress(name, frac, total_frac, total_files) # send a signal
+
+    def downloadEnd(self, name, status, msg):
+        ''' Download of af single instace ended '''
+        self.DownloadEnd(name, status, msg) # send a signal
+
+    def repoMetaDataProgress(self, name, frac):
+        ''' Repository Metadata Download progress '''
+        self.RepoMetaDataProgress( name, frac)
+
+
+
+class DnfDaemonBase(dbus.service.Object, DownloadCallback):
+
+    def __init__(self, mainloop):
+        self.logger = logging.getLogger('dnfdaemon.base')
+        self.mainloop = mainloop # use to terminate mainloop
+        self.authorized_sender = set()
+        self._lock = None
+        self._base = None
+        self._can_quit = True
+        self._is_working = False
+        self._watchdog_count = 0
+        self._watchdog_disabled = False
+        self._timeout_idle = 20         # time to daemon is closed when unlocked
+        self._timeout_locked = 600      # time to daemon is closed when locked and not working
+        self._obsoletes_list = None     # Cache for obsoletes
+
+
+    def _get_obsoletes(self):
+        ''' Cache a list of obsoletes'''
+        if not self._obsoletes_list:
+            self._obsoletes_list = list(self.base.packages.obsoletes)
+        return self._obsoletes_list
+
+    @property
+    def base(self):
+        '''
+        yumbase property so we can auto initialize it if not defined
+        '''
+        if not self._base:
+            self._get_base()
+        return self._base
+
+#===============================================================================
+# Helper methods for api methods both in system & session
+# Search -> _search etc
+#===============================================================================
+
+
+    def _search(self, fields, keys, match_all, newest_only, tags):
+        '''
+        Search for for packages, where given fields contain given key words
+        (Helper for Search)
+
+        :param fields: list of fields to search in
+        :param keys: list of keywords to search for
+        :param match_all: match all flag, if True return only packages matching all keys
+        :param newest_only: return only the newest version of a package
+        :param tags: seach pkgtags
+        '''
+        # FIXME: Add support for search in pkgtags, when supported in dnf
+        showdups = not newest_only
+        result = self.base.search(fields, keys, match_all, showdups)
+        pkg_ids = self._to_package_id_list(result)
+        return pkg_ids
+
+
+
+    def _get_packages_by_name(self, name, newest_only):
+        '''
+        Get a list of pkg ids from a name pattern
+        (Helper for GetPackagesByName)
+
+        :param name: name pattern
+        :param newest_only: True = get newest packages only
+        '''
+        qa = self._get_po_by_name(name, newest_only)
+        pkg_ids = self._to_package_id_list(qa)
+        return pkg_ids
+
+    def _get_po_by_name(self, name, newest_only):
+        '''
+        Get packages matching a name pattern
+
+        :param name: name pattern
+        :param newest_only: True = get newest packages only
+        '''
+        subj = dnf.subject.Subject(name)
+        qa = subj.get_best_query(self.base.sack, with_provides=False)
+        if newest_only:
+            qa = qa.latest()
+        return list(qa)
+
+    def _get_groups(self):
+        '''
+        make a list with categoties and there groups
+        This is the old way of yum groups, where a group is a collection of mandatory, default and optional pacakges
+        and the group is installed when all mandatory & default packages is installed.
+        '''
+        all_groups = []
+        if not self.base.comps: # lazy load the comps metadata
+            self.base.read_comps()
+        cats = self.base.comps.categories
+        for category in cats:
+            cat = (category.name, category.ui_name, category.ui_description)
+            cat_grps = []
+            for obj in category.group_ids:
+                grp = self.base.comps.group_by_pattern(obj.name) # get the dnf group obj
+                if grp:
+                    elem = (grp.id, grp.ui_name, grp.ui_description, grp.installed)
+                    cat_grps.append(elem)
+            cat_grps.sort()
+            all_groups.append((cat, cat_grps))
+        all_groups.sort()
+        return json.dumps(all_groups)
+
+    def _get_repositories(self, filter):
+        '''
+        Get the value a list of repo ids
+        :param filter: filter to limit the listed repositories
+        '''
+        if filter == '' or filter == 'enabled':
+            repos = [repo.id for repo in self.base.repos.iter_enabled()]
+        else:
+            repos = [repo.id for repo in  self.base.repos.get_matching(filter)]
+        return repos
+
+
+    def _get_config(self, setting):
+        '''
+        Get the value of a yum config setting
+        it will return a JSON string of the config
+        :param setting: name of setting (debuglevel etc..)
+        '''
+        if setting == '*': # Return all config
+            cfg = self.base.conf
+            all_conf = dict([(c,getattr(cfg,c)) for c in cfg.iterkeys()])
+            value =  json.dumps(all_conf)
+        elif hasattr(self.base.conf, setting):
+            value = json.dumps(getattr(self.base.conf, setting))
+        else:
+            value = json.dumps(None)
+        return value
+        return value
+
+    def _get_repo(self, repo_id ):
+        '''
+        Get information about a give repo_id
+        the repo setting will be returned as dictionary in JSON format
+        :param repo_id:
+        '''
+        value = json.dumps(None)
+        repo = self.base.repos.get(repo_id, None) # get the repo object
+        if repo:
+            repo_conf = dict([(c,getattr(repo,c)) for c in repo.iterkeys()])
+            value = json.dumps(repo_conf)
+        return value
+
+    def _get_packages(self, pkg_filter):
+        '''
+        Get a list of package ids, based on a package pkg_filterer
+        :param pkg_filter: pkg pkg_filter string ('installed','updates' etc)
+        '''
+        if pkg_filter in ['installed','available','updates','obsoletes','recent','extras']:
+            pkgs = getattr(self.base.packages,pkg_filter)
+            value = self._to_package_id_list(pkgs)
+        else:
+            value = []
+        return value
+
+    def _get_package_with_attributes(self, pkg_filter, fields):
+        '''
+        Get a list of package ids, based on a package pkg_filterer
+        :param pkg_filter: pkg pkg_filter string ('installed','updates' etc)
+        '''
+        value = []
+        if pkg_filter in ['installed','available','updates','obsoletes','recent','extras']:
+            pkgs = getattr(self.base.packages,pkg_filter)
+            value = [self._get_po_list(po,fields) for po in pkgs]
+        return value
+
+    def _get_attribute(self, id, attr):
+        '''
+        Get an attribute from a yum package id
+        it will return a python repr string of the attribute
+        :param id: yum package id
+        :param attr: name of attribute (summary, size, description, changelog etc..)
+        '''
+        po = self._get_po(id)
+        if po:
+            if attr in FAKE_ATTR: # is this a fake attr:
+                value = json.dumps(self._get_fake_attributes(po, attr))
+            elif hasattr(po, attr):
+                value = json.dumps(getattr(po,attr))
+            else:
+                value = json.dumps(None)
+        else:
+            value = json.dumps(None)
+        return value
+
+    def _get_installed_na(self,name,arch):
+        q = self.base.sack.query()
+        inst = q.installed().filter(name=po.name, arch=po.arch).run()
+        if inst:
+            return inst[0]
+        else:
+            return None
+
+
+    def _get_updateInfo(self, id):
+        '''
+        Get an Update Infomation e from a yum package id
+        it will return a python repr string of the attribute
+        :param id: yum package id
+        '''
+        po = self._get_po(id)
+        if po:
+            # TODO : updateinfo is not supported in DNF yet
+            # https://bugzilla.redhat.com/show_bug.cgi?id=850912
+            value = json.dumps(None)
+        else:
+            value = json.dumps(None)
+        return value
+
+
+
+    def _get_group_pkgs(self, grp_id, grp_flt):
+        '''
+        Get packages for a given grp_id and group filter
+        '''
+        pkgs = []
+        grp = self.base.comps.group_by_pattern(grp_id)
+        if grp:
+            if grp_flt == 'all':
+                pkg_names = []
+                pkg_names.extend([p.name for p in grp.mandatory_packages ])
+                pkg_names.extend([p.name for p in grp.default_packages ])
+                pkg_names.extend([p.name for p in grp.optional_packages ])
+                best_pkgs = []
+                for name in pkg_names:
+                    best_pkgs.extend(self._get_po_by_name(name,True))
+            else:
+                pkg_names = []
+                pkg_names.extend([p.name for p in grp.mandatory_packages ])
+                pkg_names.extend([p.name for p in grp.default_packages ])
+                best_pkgs = []
+                for name in pkg_names:
+                    best_pkgs.extend(self._get_po_by_name(name,True))
+            pkgs = self.base.packages.filter_packages(best_pkgs)
+        else:
+            pass
+        pkg_ids = self._to_package_id_list(pkgs)
+        return pkg_ids
+
+#===============================================================================
+# Helper methods
+#===============================================================================
+
+    def _get_po_list(self, po, attrs):
+        po_list = [self._get_id(po)]
+        for attr in attrs:
+            if attr in FAKE_ATTR: # is this a fake attr:
+                value = self._get_fake_attributes(po, attr)
+            elif hasattr(po, attr):
+                value = getattr(po,attr)
+            else:
+                value = None
+            po_list.append(value)
+        return po_list
+
+    def _get_id_time_list(self, hist_trans):
+        '''
+        return a list of (tid, isodate) pairs from a list of yum history transactions
+        '''
+        result = []
+        for ht in hist_trans:
+            tm = datetime.fromtimestamp(ht.end_timestamp)
+            result.append((ht.tid, tm.isoformat()))
+        return result
+
+    def _get_fake_attributes(self,po, attr):
+        '''
+        Get Fake Attributes, a whey to useful stuff for a package there is not real
+        attritbutes to the yum package object.
+        :param attr: Fake attribute
+        :type attr: string
+        '''
+        if attr == "action":
+            return self._get_action(po)
+        elif attr == 'downgrades':
+            return self._get_downgrades(po)
+        elif attr == 'pkgtags':
+            return self._get_pkgtags(po)
+        elif attr == 'changelog':
+            # TODO : changelog is not supported in DNF yet
+            # https://bugzilla.redhat.com/show_bug.cgi?id=1066867
+            return None
+
+    def _get_downgrades(self,pkg):
+        pkg_ids = []
+        ''' Find available downgrades for a given name.arch'''
+        q = self.base.sack.query()
+        avail = q.available().filter(name=pkg.name, arch=pkg.arch).run()
+        for apkg in avail:
+            if pkg.evr_gt(apkg):
+                pkg_ids.append(self._get_id(apkg))
+        return pkg_ids
+
+    def _get_pkgtags(self, po):
+        '''
+        Get pkgtags from a given po
+        '''
+        # TODO : pkgtags is not supported in DNF yet
+        return []
+
+    def _to_package_id_list(self, pkgs):
+        '''
+        return a sorted list of package ids from a list of packages
+        if and po is installed, the installed po id will be returned
+        :param pkgs:
+        '''
+        result = set()
+        for po in sorted(pkgs):
+            result.add(self._get_id(po))
+        return result
+
+    def _get_po(self,id):
+        ''' find the real package from an package id'''
+        n, e, v, r, a, repo_id = id.split(',')
+        q = self.base.sack.query()
+        if repo_id.startswith('@'): # installed package
+            f = q.installed()
+            f = f.filter(name=n, version=v, release=r, arch=a)
+            if len(f) > 0:
+                return f[0]
+            else:
+                return None
+        else:
+            f = q.available()
+            f = f.filter(name=n, version=v, release=r, arch=a)
+            if len(f) > 0:
+                return f[0]
+            else:
+                return None
+
+    def _get_id(self,pkg):
+        '''
+        convert a yum package obejct to an id string containing (n,e,v,r,a,repo)
+        :param pkg:
+        '''
+        values = [pkg.name, str(pkg.epoch), pkg.version, pkg.release, pkg.arch, pkg.ui_from_repo]
+        return ",".join(values)
+
+
+    def _get_action(self, po):
+        '''
+        Return the available action for a given pkg_id
+        The action is what can be performed on the package
+        an installed package will return as 'remove' as action
+        an available update will return 'update'
+        an available package will return 'install'
+        :param po: yum package
+        :type po: yum package object
+        :return: action (remove, install, update, downgrade, obsolete)
+        :rtype: string
+        '''
+        action = 'install'
+        n, a, e, v, r = po.pkgtup
+        q = self.base.sack.query()
+        if po.reponame.startswith('@'):
+            action = 'remove'
+        else:
+            upd = q.upgrades().filter(name=n, version=v, release=r, arch=a)
+            if upd:
+                action = 'update'
+            else:
+                obsoletes = self._get_obsoletes()
+                if po in obsoletes:
+                    action = 'obsolete'
+                else:
+                    # get installed packages with same name
+                    ipkgs = q.installed().filter(name=po.name).run()
+                    if ipkgs:
+                        ipkg = ipkgs[0]
+                        if po.evr_gt(ipkg):
+                            action = 'downgrade'
+        return action
+
+    def _get_base(self):
+        '''
+        Get a Dnf Base object to work with
+        '''
+        if not self._base:
+            self._base = DnfBase(self)
+        return self._base
+
+
+    def _reset_base(self):
+        '''
+        destroy the current YumBase object
+        '''
+        del self._base
+        self._base = None
+
+
+    def _setup_watchdog(self):
+        '''
+        Setup the watchdog to run every second when idle
+        '''
+        gobject.timeout_add(1000, self._watchdog)
+
+    def _watchdog(self):
+        terminate = False
+        if self._watchdog_disabled or self._is_working: # is working
+            return True
+        if not self._lock: # is locked
+            if self._watchdog_count > self._timeout_idle:
+                terminate = True
+        else:
+            if self._watchdog_count > self._timeout_locked:
+                terminate = True
+        if terminate: # shall we quit
+            if self._can_quit:
+                self._reset_base()
+                self.mainloop.quit()
+        else:
+            self._watchdog_count += 1
+            self.logger.debug("Watchdog : %i" % self._watchdog_count )
+            return True
+
+class Packages:
+
+    def __init__(self, base):
+        self._base = base
+        self._sack = base.sack
+        self._inst_na = self._sack.query().installed().na_dict()
+
+    def filter_packages(self, pkg_list, replace=True):
+        '''
+        Filter a list of package objects and replace
+        the installed ones with the installed object, instead
+        of the available object
+        '''
+        pkgs = []
+        for pkg in pkg_list:
+            key = (pkg.name, pkg.arch)
+            inst_pkg = self._inst_na.get(key, [None])[0]
+            if inst_pkg and inst_pkg.evr == pkg.evr:
+                if replace:
+                    pkgs.append(inst_pkg)
+            else:
+                pkgs.append(pkg)
+        return pkgs
+
+
+    @property
+    def query(self):
+        return self._sack.query()
+
+    @property
+    def installed(self):
+        '''
+        instawlled packages
+        '''
+        return self.query.installed().run()
+
+    @property
+    def updates(self):
+        '''
+        available updates
+        '''
+        return self.query.upgrades().run()
+
+
+    @property
+    def all(self,showdups = False):
+        '''
+        all packages in the repositories
+        installed ones are replace with the install package objects
+        '''
+        if showdups:
+            return self.filter_packages(self.query.available().run())
+        else:
+            return self.filter_packages(self.query.latest().run())
+
+    @property
+    def available(self, showdups = False):
+        '''
+        available packages there is not installed yet
+        '''
+        if showdups:
+            return self.filter_packages(self.query.available().run(), replace=False)
+        else:
+            return self.filter_packages(self.query.latest().run(), replace=False)
+
+    @property
+    def extras(self):
+        '''
+        installed packages, not in current repos
+        '''
+        # anything installed but not in a repo is an extra
+        avail_dict = self.query.available().pkgtup_dict()
+        inst_dict = self.query.installed().pkgtup_dict()
+        pkgs = []
+        for pkgtup in inst_dict:
+            if pkgtup not in avail_dict:
+                pkgs.extend(inst_dict[pkgtup])
+        return pkgs
+
+    @property
+    def obsoletes(self):
+        '''
+        packages there is obsoleting some installed packages
+        '''
+        inst = self.query.installed()
+        return self.query.filter(obsoletes=inst)
+
+    @property
+    def recent(self, showdups=False):
+        recent = []
+        now = time()
+        recentlimit = now-(self._base.conf.recent*86400)
+        if showdups:
+            avail = self.query.available()
+        else:
+            avail = self.query.latest()
+        for po in avail:
+            if int(po.buildtime) > recentlimit:
+                recent.append(po)
+        return recent
+
+
+class DnfBase(dnf.Base):
+
+    def __init__(self, parent):
+        dnf.Base.__init__(self)
+        self.parent = parent
+        self.md_progress = MDProgress(parent)
+        self.setup_cache()
+        self.read_all_repos()
+        self.progress = Progress(parent)
+        self.repos.all().set_progress_bar( self.md_progress)
+        self.fill_sack()
+        self._packages = Packages(self)
+
+    @property
+    def packages(self):
+        return self._packages
+
+    def setup_cache(self):
+        # perform the CLI-specific cachedir tricks
+        conf = self.conf
+        #conf.read() # Read the conf file from disk
+        conf.releasever = '20' # FIXME: dont hardcode fedora release
+        # conf.cachedir = CACHE_DIR # hardcoded cache dir
+        # This is not public API, but we want the same cache as dnf cli
+        suffix = dnf.yum.parser.varReplace(dnf.const.CACHEDIR_SUFFIX, conf.yumvar)
+        cli_cache = dnf.conf.CliCache(conf.cachedir, suffix)
+        conf.cachedir = cli_cache.cachedir
+        self._system_cachedir = cli_cache.system_cachedir
+        print("cachedir: %s" % conf.cachedir)
+
+    def apply_transaction(self):
+        ''' apply the current transaction to the system'''
+        rc = self.resolve()
+        if rc:
+            to_dnl = self.get_packages_to_download()
+            # Downloading Packages
+            self.download_packages(to_dnl, self.progress)
+            rc, msg = self.do_transaction()
+            if rc <> 0:
+                return (False, "transaction-error", msg)
+            else:
+                return (True, "transaction-ok","")
+        else:
+            return (False, "depsolve-failed", "")
+
+    def get_packages_to_download(self):
+        ''' Get a list of packages to download from the current transaction'''
+        to_dnl = []
+        for tsi in self.transaction:
+            if tsi.installed:
+                to_dnl.append(tsi.installed)
+        return to_dnl
+
+    def search(self, fields, values, match_all=True, showdups=False):
+        '''
+        search in a list of package fields for a list of keys
+        :param fields: package attributes to search in
+        :param values: the values to match
+        :param match_all: match all values (default)
+        :param showdups: show duplicate packages or latest (default)
+        :return: a list of package objects
+        '''
+        matches = set()
+        for key in values:
+            key_set = set()
+            for attr in fields:
+                pkgs = set(self.contains(attr,key).run())
+                key_set |= pkgs
+            if len(matches) == 0:
+                matches = key_set
+            else:
+                if match_all:
+                    matches &= key_set
+                else:
+                    matches |= key_set
+        result = list(matches)
+        if not showdups:
+            result = self.sack.query().filter(pkg=result).latest()
+        return result
+
+    def contains(self, attr, needle, ignore_case=True):
+        fdict = {'%s__substr' % attr : needle}
+        if ignore_case:
+            return self.sack.query().filter(hawkey.ICASE, **fdict)
+        else:
+            return self.sack.query().filter(**fdict)
+
+
+class MDProgress(DownloadProgress):
+
+    def __init__(self, parent):
+        super(MDProgress, self).__init__()
+        self._last = -1.0
+        self.parent = parent
+
+    def start(self, total_files, total_size):
+        self._last = -1.0
+
+    def end(self,payload, status, msg):
+        name  = str(payload)
+        if status == STATUS_OK:
+            self.parent.repoMetaDataProgress(name, 1.0)
+
+    def progress(self, payload, done):
+        name  = str(payload)
+        cur_total_bytes = payload.download_size
+        if cur_total_bytes:
+            frac = done/float(cur_total_bytes)
+        else:
+            frac = 0.0
+        if frac > self._last+0.01:
+            self._last = frac
+            self.parent.repoMetaDataProgress(name, frac)
+
+
+class Progress(DownloadProgress):
+
+    def __init__(self, parent):
+        super(Progress, self).__init__()
+        self.parent = parent
+        self.total_files = 0
+        self.total_size = 0.0
+        self.download_files = 0
+        self.download_size = 0.0
+        self.dnl = {}
+        self.last_frac = 0
+
+    def start(self, total_files, total_size):
+        self.total_files = total_files
+        self.total_size = total_size
+        self.download_files = 0
+        self.download_size = 0.0
+        self.parent.downloadStart(total_files, total_size)
+
+
+    def end(self,payload, status, msg):
+        if not status: # payload download complete
+            self.download_files += 1
+        self.parent.downloadEnd(str(payload), status, msg)
+
+    def progress(self, payload, done):
+        pload = str(payload)
+        cur_total_bytes = payload.download_size
+        if not pload in self.dnl:
+            self.dnl[pload] = 0.0
+        else:
+            self.dnl[pload] = done
+            total_frac = self.get_total()
+            if total_frac > self.last_frac:
+                self.last_frac = total_frac
+                if cur_total_bytes:
+                    frac = done / cur_total_bytes
+                else:
+                    frac = 0.0
+                self.parent.downloadProgress(pload, frac, total_frac, self.download_files)
+
+    def get_total(self):
+        """ Get the total downloaded percentage"""
+        tot = 0.0
+        for value in self.dnl.values():
+            tot += value
+        frac = int((tot / float(self.total_size)))
+        return frac
+
+    def update(self):
+        """ Output the current progress"""
+
+        sys.stdout.write("Progress : %-3d %% (%d/%d)\r" % (self.last_pct,self.download_files, self.total_files))
+
+
+class TransactionDisplay(object):
+
+    def __init__(self):
+        self.last = -1
+
+    def event(self, package, action, te_current, te_total, ts_current, ts_total):
+        """
+        @param package: A yum package object or simple string of a package name
+        @param action: A constant transaction set state
+        @param te_current: current number of bytes processed in the transaction
+                           element being processed
+        @param te_total: total number of bytes in the transaction element being
+                         processed
+        @param ts_current: number of processes completed in whole transaction
+        @param ts_total: total number of processes in the transaction.
+        """
+        # this is where a progress bar would be called
+
+        if te_total and te_total > 0:
+            percent = int((float(te_current)/te_total)*100.0)
+            if percent == 100:
+                self.last=-1
+                print(action, package, percent, ts_current, ts_total )
+            elif percent > self.last and percent % 10 == 0:
+                self.last = percent
+                print(action, package, percent, ts_current, ts_total )
+
+        else:
+            print(action, package)
+
+    def scriptout(self, msgs):
+        """msgs is the messages that were output (if any)."""
+        if msgs:
+            print("ScriptOut: ",msgs)
+
+    def errorlog(self, msg):
+        """takes a simple error msg string"""
+        print(msg, file=sys.stderr)
+
+    def filelog(self, package, action):
+        # check package object type - if it is a string - just output it
+        """package is the same as in event() - a package object or simple string
+           action is also the same as in event()"""
+        pass
+
+    def verify_tsi_package(self, pkg, count, total):
+        print("Verifing : %s "% pkg)
+
+
+
+
+def doTextLoggerSetup(logroot='dnfdaemon', logfmt='%(asctime)s: %(message)s', loglvl=logging.INFO):
+    ''' Setup Python logging  '''
+    logger = logging.getLogger(logroot)
+    logger.setLevel(loglvl)
+    formatter = logging.Formatter(logfmt, "%H:%M:%S")
+    handler = logging.StreamHandler(stream=sys.stdout)
+    handler.setFormatter(formatter)
+    handler.propagate = False
+    logger.addHandler(handler)
+
+
diff --git a/dnfdaemon/dnfdaemon-session.py b/dnfdaemon/dnfdaemon-session.py
new file mode 100755
index 0000000000000000000000000000000000000000..66b6f05eebcc87942418decd88d973a461903c3f
--- /dev/null
+++ b/dnfdaemon/dnfdaemon-session.py
@@ -0,0 +1,440 @@
+#!/usr/bin/python -tt
+#coding: utf-8
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+# (C) 2013 - Tim Lauridsen <timlau@fedoraproject.org>
+
+
+#
+# dnf session bus dBus service (Readonly)
+#
+
+from __future__ import print_function
+from __future__ import absolute_import
+import dbus
+import dbus.service
+import dbus.glib
+import gobject
+import json
+import logging
+
+import argparse
+
+import sys
+import os
+
+from common import DnfDaemonBase, doTextLoggerSetup, Logger, DownloadCallback, FAKE_ATTR, NONE, \
+                   TransactionDisplay
+
+version = 902 #  (00.09.02) must be integer
+DAEMON_ORG = 'org.baseurl.DnfSession'
+DAEMON_INTERFACE = DAEMON_ORG
+FAKE_ATTR = ['downgrades','action','pkgtags']
+NONE = json.dumps(None)
+
+def _(msg):
+    return msg
+
+#------------------------------------------------------------------------------ DBus Exception
+class AccessDeniedError(dbus.DBusException):
+    _dbus_error_name = DAEMON_ORG+'.AccessDeniedError'
+
+class LockedError(dbus.DBusException):
+    _dbus_error_name = DAEMON_ORG+'.LockedError'
+
+class NotImplementedError(dbus.DBusException):
+    _dbus_error_name = DAEMON_ORG+'.NotImplementedError'
+
+
+logger = logging.getLogger('dnfdaemon.session')
+
+#------------------------------------------------------------------------------ Main class
+class DnfDaemon(DnfDaemonBase):
+
+    def __init__(self, mainloop):
+        DnfDaemonBase.__init__(self,  mainloop)
+        self.logger = logging.getLogger('dnfdaemon-session')
+        bus_name = dbus.service.BusName(DAEMON_ORG, bus = dbus.SessionBus())
+        dbus.service.Object.__init__(self, bus_name, '/')
+
+#===============================================================================
+# DBus Methods
+#===============================================================================
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='',
+                                          out_signature='i')
+    def GetVersion(self):
+        '''
+        Get the daemon version
+        '''
+        return version
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='',
+                                          out_signature='b',
+                                          sender_keyword='sender')
+    def Exit(self, sender=None):
+        '''
+        Exit the daemon
+        :param sender:
+        '''
+        if self._can_quit:
+            self.mainloop.quit()
+            return True
+        else:
+            return False
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='',
+                                          out_signature='b',
+                                          sender_keyword='sender')
+    def Lock(self, sender=None):
+        '''
+        Get the yum lock
+        :param sender:
+        '''
+        if not self._lock:
+            self._lock = sender
+            self.logger.info('LOCK: Locked by : %s' % sender)
+            return True
+        return False
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='b',
+                                          out_signature='b',
+                                          sender_keyword='sender')
+
+    def SetWatchdogState(self,state, sender=None):
+        '''
+        Set the Watchdog state
+        :param state: True = Watchdog active, False = Watchdog disabled
+        :type state: boolean (b)
+        '''
+        self._watchdog_disabled = not state
+        return state
+
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='s',
+                                          out_signature='as',
+                                          sender_keyword='sender')
+
+    def GetRepositories(self, filter, sender=None):
+        '''
+        Get the value a list of repo ids
+        :param filter: filter to limit the listed repositories
+        :param sender:
+        '''
+        self.working_start(sender)
+        repos = self._get_repositories(filter)
+        return self.working_ended(repos)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='as',
+                                          out_signature='',
+                                          sender_keyword='sender')
+
+    def SetEnabledRepos(self, repo_ids, sender=None):
+        '''
+        Enabled a list of repositories, disabled all other repos
+        :param repo_ids: list of repo ids to enable
+        :param sender:
+        '''
+        self.working_start(sender)
+        #  TODO : Add dnf code
+        return self.working_ended()
+
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='s',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def GetConfig(self, setting ,sender=None):
+        '''
+        Get the value of a yum config setting
+        it will return a JSON string of the config
+        :param setting: name of setting (debuglevel etc..)
+        :param sender:
+        '''
+        self.working_start(sender)
+        value = self._get_config(setting)
+        return self.working_ended(value)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='s',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def GetRepo(self, repo_id ,sender=None):
+        '''
+        Get information about a give repo_id
+        the repo setting will be returned as dictionary in JSON format
+        :param repo_id:
+        :param sender:
+        '''
+        self.working_start(sender)
+        value = self._get_repo(repo_id)
+        return self.working_ended(value)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='s',
+                                          out_signature='as',
+                                          sender_keyword='sender')
+    def GetPackages(self, pkg_filter, sender=None):
+        '''
+        Get a list of package ids, based on a package pkg_filterer
+        :param pkg_filter: pkg pkg_filter string ('installed','updates' etc)
+        :param sender:
+        '''
+        self.working_start(sender)
+        value = self._get_packages(pkg_filter)
+        return self.working_ended(value)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='sas',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def GetPackageWithAttributes(self, pkg_filter, fields, sender=None):
+        '''
+        Get a list of package ids, based on a package pkg_filterer
+        :param pkg_filter: pkg pkg_filter string ('installed','updates' etc)
+        :param sender:
+        '''
+        self.working_start(sender)
+        value = self._get_package_with_attributes(pkg_filter, fields)
+        return self.working_ended(json.dumps(value))
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='sb',
+                                          out_signature='as',
+                                          sender_keyword='sender')
+    def GetPackagesByName(self, name, newest_only, sender=None):
+        '''
+        Get a list of packages from a name pattern
+        :param name: name pattern
+        :param newest_only: True = get newest packages only
+        :param sender:
+        '''
+        self.working_start(sender)
+        pkg_ids = self._get_packages_by_name(name, newest_only)
+        return self.working_ended(pkg_ids)
+
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='ss',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def GetAttribute(self, id, attr,sender=None):
+        '''
+        Get an attribute from a yum package id
+        it will return a python repr string of the attribute
+        :param id: yum package id
+        :param attr: name of attribute (summary, size, description, changelog etc..)
+        :param sender:
+        '''
+        self.working_start(sender)
+        value = self._get_attribute( id, attr)
+        return self.working_ended(value)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='s',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def GetUpdateInfo(self, id,sender=None):
+        '''
+        Get an Update Infomation e from a yum package id
+        it will return a python repr string of the attribute
+        :param id: yum package id
+        :param sender:
+        '''
+        self.working_start(sender)
+        value = self._get_updateInfo(id)
+        return self.working_ended(value)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='',
+                                          out_signature='b',
+                                          sender_keyword='sender')
+    def Unlock(self, sender=None):
+        ''' release the lock'''
+        if self.check_lock(sender):
+            #  TODO : Add dnf code
+            self.logger.info('UNLOCK: Lock Release by %s' % self._lock)
+            self._lock = None
+            return True
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='asasbbb',
+                                          out_signature='as',
+                                          sender_keyword='sender')
+    def Search(self, fields, keys, match_all, newest_only, tags, sender=None ):
+        '''
+        Search for for packages, where given fields contain given key words
+        :param fields: list of fields to search in
+        :param keys: list of keywords to search for
+        :param match_all: match all flag, if True return only packages matching all keys
+        :param newest_only: return only the newest version of a package
+        :param tags: seach pkgtags
+        '''
+        self.working_start(sender)
+        result = self._search(fields, keys, match_all, newest_only, tags)
+        return self.working_ended(result)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def GetGroups(self, sender=None ):
+        '''
+        Return a category/group tree
+        '''
+        self.working_start(sender)
+        value = self._get_groups()
+        return self.working_ended(value)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='ss',
+                                          out_signature='as',
+                                          sender_keyword='sender')
+    def GetGroupPackages(self, grp_id, grp_flt, sender=None ):
+        '''
+        Get packages in a group by grp_id and grp_flt
+        :param grp_id: The Group id
+        :param grp_flt: Group Filter (all or default)
+        :param sender:
+        '''
+        self.working_start(sender)
+        pkg_ids = self._get_group_pkgs(grp_id, grp_flt)
+        return self.working_ended(pkg_ids)
+
+
+
+#
+#  Template for new method
+#
+#    @dbus.service.method(DAEMON_INTERFACE,
+#                                          in_signature='',
+#                                          out_signature='',
+#                                          sender_keyword='sender')
+#    def NewMethod(self, sender=None ):
+#        '''
+#
+#        '''
+#        self.working_start(sender)
+#        value = True
+#        return self.working_ended(value)
+#
+
+
+#===============================================================================
+# DBus signals
+#===============================================================================
+    @dbus.service.signal(DAEMON_INTERFACE)
+    def UpdateProgress(self,name,frac,fread,ftime):
+        '''
+        DBus signal with download progress information
+        will send dbus signals with download progress information
+        :param name: filename
+        :param frac: Progress fracment (0 -> 1)
+        :param fread: formated string containing BytesRead
+        :param ftime : formated string containing remaining or elapsed time
+        '''
+        pass
+
+# Parallel Download Progress signals
+
+    @dbus.service.signal(DAEMON_INTERFACE)
+    def DownloadStart(self, num_files, num_bytes):
+        ''' Starting a new parallel download batch '''
+        pass
+
+    @dbus.service.signal(DAEMON_INTERFACE)
+    def DownloadProgress(self, name, frac, total_frac, total_files):
+        ''' Progress for a single instance in the batch '''
+        pass
+
+    @dbus.service.signal(DAEMON_INTERFACE)
+    def DownloadEnd(self, name, status, msg):
+        ''' Download of af single instace ended '''
+        pass
+
+    @dbus.service.signal(DAEMON_INTERFACE)
+    def RepoMetaDataProgress(self, name, frac):
+        ''' Repository Metadata Download progress '''
+
+
+#===============================================================================
+# Helper methods
+#===============================================================================
+    def working_start(self,sender):
+        self.check_lock(sender)
+        self._is_working = True
+        self._watchdog_count = 0
+
+    def working_ended(self, value=None):
+        self._is_working = False
+        return value
+
+    def check_lock(self, sender):
+        '''
+        Check that the current sender is owning the yum lock
+        :param sender:
+        '''
+        if self._lock == sender:
+            return True
+        else:
+            raise LockedError('dnf is locked by another application')
+
+
+
+def main():
+    parser = argparse.ArgumentParser(description='Yum D-Bus Session Daemon')
+    parser.add_argument('-v', '--verbose', action='store_true')
+    parser.add_argument('-d', '--debug', action='store_true')
+    parser.add_argument('--notimeout', action='store_true')
+    args = parser.parse_args()
+    if args.verbose:
+        if args.debug:
+            doTextLoggerSetup(logroot='dnfdaemon',loglvl=logging.DEBUG)
+        else:
+            doTextLoggerSetup(logroot='dnfdaemon')
+
+    # setup the DBus mainloop
+    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+    mainloop = gobject.MainLoop()
+    yd = DnfDaemon(mainloop)
+    if not args.notimeout:
+        yd._setup_watchdog()
+    mainloop.run()
+
+if __name__ == '__main__':
+    main()
diff --git a/dnfdaemon/dnfdaemon-system.py b/dnfdaemon/dnfdaemon-system.py
new file mode 100755
index 0000000000000000000000000000000000000000..af30a87715336d37477638da03e7d92f7a814c2d
--- /dev/null
+++ b/dnfdaemon/dnfdaemon-system.py
@@ -0,0 +1,954 @@
+#!/usr/bin/python -tt
+#coding: utf-8
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+# (C) 2013 - Tim Lauridsen <timlau@fedoraproject.org>
+
+import dbus
+import dbus.service
+import dbus.glib
+import gobject
+import json
+import logging
+from datetime import datetime
+
+import argparse
+
+from common import DaemonBase, doTextLoggerSetup, Logger, DownloadCallback, NONE, FAKE_ATTR
+
+version = 902 #  (00.09.02) must be integer
+DAEMON_ORG = 'org.baseurl.DnfSystem'
+DAEMON_INTERFACE = DAEMON_ORG
+
+def _(msg):
+    return msg
+
+#------------------------------------------------------------------------------ DBus Exception
+class AccessDeniedError(dbus.DBusException):
+    _dbus_error_name = DAEMON_ORG+'.AccessDeniedError'
+
+class YumLockedError(dbus.DBusException):
+    _dbus_error_name = DAEMON_ORG+'.LockedError'
+
+class YumTransactionError(dbus.DBusException):
+    _dbus_error_name = DAEMON_ORG+'.TransactionError'
+
+class YumNotImplementedError(dbus.DBusException):
+    _dbus_error_name = DAEMON_ORG+'.NotImplementedError'
+
+#------------------------------------------------------------------------------ Callback handlers
+
+class ProcessTransCallback:
+    STATES = { PT_DOWNLOAD      : "download",
+               PT_DOWNLOAD_PKGS : "pkg-to-download",
+               PT_GPGCHECK      : "signature-check",
+               PT_TEST_TRANS    : "run-test-transaction",
+               PT_TRANSACTION   : "run-transaction"}
+
+    def __init__(self, base):
+        self.base = base
+
+    def event(self,state,data=NONE):
+        if state in ProcessTransCallback.STATES:
+            if data != NONE:
+                data = [self.base._get_id(po) for po in data]
+            self.base.TransactionEvent(ProcessTransCallback.STATES[state], data)
+
+class RPMCallback(RPMBaseCallback):
+    '''
+    RPMTransaction display callback class
+    '''
+    ACTIONS = { TS_UPDATE : 'update',
+                TS_ERASE: 'erase',
+                TS_INSTALL: 'install',
+                TS_TRUEINSTALL : 'install',
+                TS_OBSOLETED: 'obsolete',
+                TS_OBSOLETING: 'install',
+                TS_UPDATED: 'cleanup',
+                'repackaging': 'repackage'}
+
+    def __init__(self, base):
+        RPMBaseCallback.__init__(self)
+        self.base = base
+
+    def event(self, package, action, te_current, te_total, ts_current, ts_total):
+        """
+        :param package: A yum package object or simple string of a package name
+        :param action: A yum.constant transaction set state or in the obscure
+                       rpm repackage case it could be the string 'repackaging'
+        :param te_current: Current number of bytes processed in the transaction
+                           element being processed
+        :param te_total: Total number of bytes in the transaction element being
+                         processed
+        :param ts_current: number of processes completed in whole transaction
+        :param ts_total: total number of processes in the transaction.
+        """
+        if not isinstance(package, str): # package can be both str or yum package object
+            id = self.base._get_id(package)
+        else:
+            id = package
+        if action in RPMCallback.ACTIONS:
+            action = RPMCallback.ACTIONS[action]
+        self.base.RPMProgress(id, action, te_current, te_total, ts_current, ts_total)
+
+    def scriptout(self, package, msgs):
+        """package is the package.  msgs is the messages that were
+        output (if any)."""
+        pass
+
+
+class DaemonBase():
+
+    def __init__(self, daemon):
+        self._daemon = daemon
+
+    def _checkSignatures(self,pkgs,callback):
+        ''' The the signatures of the downloaded packages '''
+        return 0
+
+
+logger = logging.getLogger('yumdaemon')
+
+#------------------------------------------------------------------------------ Main class
+class DnfDaemon(DaemonBase):
+
+    def __init__(self, mainloop):
+        DaemonBase.__init__(self,  mainloop)
+        self.logger = logging.getLogger('dnfdaemon.system')
+        bus_name = dbus.service.BusName(DAEMON_ORG, bus = dbus.SystemBus())
+        dbus.service.Object.__init__(self, bus_name, '/')
+        self._gpg_confirm = {}
+
+#===============================================================================
+# DBus Methods
+#===============================================================================
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='',
+                                          out_signature='i')
+    def GetVersion(self):
+        '''
+        Get the daemon version
+        '''
+        return version
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='',
+                                          out_signature='b',
+                                          sender_keyword='sender')
+    def Exit(self, sender=None):
+        '''
+        Exit the daemon
+        :param sender:
+        '''
+        self.check_permission(sender)
+        if self._can_quit:
+            self._reset_base()
+            self.mainloop.quit()
+            return True
+        else:
+            return False
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='',
+                                          out_signature='b',
+                                          sender_keyword='sender')
+    def Lock(self, sender=None):
+        '''
+        Get the yum lock
+        :param sender:
+        '''
+        self.check_permission(sender)
+        if not self._lock:
+            self._lock = sender
+            self.logger.info('LOCK: Locked by : %s' % sender)
+            return True
+        return False
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='b',
+                                          out_signature='b',
+                                          sender_keyword='sender')
+    def SetWatchdogState(self,state, sender=None):
+        '''
+        Set the Watchdog state
+        :param state: True = Watchdog active, False = Watchdog disabled
+        :type state: boolean (b)
+        '''
+        self.check_permission(sender)
+        self._watchdog_disabled = not state
+        return state
+
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='s',
+                                          out_signature='as',
+                                          sender_keyword='sender')
+
+    def GetRepositories(self, filter, sender=None):
+        '''
+        Get the value a list of repo ids
+        :param filter: filter to limit the listed repositories
+        :param sender:
+        '''
+        self.working_start(sender)
+        repos = self._get_repositories(filter)
+        return self.working_ended(repos)
+
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='as',
+                                          out_signature='',
+                                          sender_keyword='sender')
+
+    def SetEnabledRepos(self, repo_ids, sender=None):
+        '''
+        Enabled a list of repositories, disabled all other repos
+        :param repo_ids: list of repo ids to enable
+        :param sender:
+        '''
+        self.working_start(sender)
+        # TODO : Add dnf code (SetEnabledRepos)
+        return self.working_ended()
+
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='s',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def GetConfig(self, setting ,sender=None):
+        '''
+        Get the value of a yum config setting
+        it will return a JSON string of the config
+        :param setting: name of setting (debuglevel etc..)
+        :param sender:
+        '''
+        self.working_start(sender)
+        value = self._get_config(setting)
+        return self.working_ended(value)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='ss',
+                                          out_signature='b',
+                                          sender_keyword='sender')
+    def SetConfig(self, setting, value ,sender=None):
+        '''
+        Set yum config setting for the running session
+        :param setting: yum conf setting to set
+        :param value: value to set
+        :param sender:
+        '''
+        self.working_start(sender)
+        rc = self._set_option(setting, json.loads(value))
+        return self.working_ended(rc)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='s',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def GetRepo(self, repo_id ,sender=None):
+        '''
+        Get information about a give repo_id
+        the repo setting will be returned as dictionary in JSON format
+        :param repo_id:
+        :param sender:
+        '''
+        self.working_start(sender)
+        value = self._get_repo(repo_id)
+        return self.working_ended(value)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='s',
+                                          out_signature='as',
+                                          sender_keyword='sender')
+    def GetPackages(self, pkg_filter, sender=None):
+        '''
+        Get a list of package ids, based on a package pkg_filterer
+        :param pkg_filter: pkg pkg_filter string ('installed','updates' etc)
+        :param sender:
+        '''
+        self.working_start(sender)
+        value = self._get_packages(pkg_filter)
+        return self.working_ended(value)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='sas',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def GetPackageWithAttributes(self, pkg_filter, fields, sender=None):
+        '''
+        Get a list of package ids, based on a package pkg_filterer
+        :param pkg_filter: pkg pkg_filter string ('installed','updates' etc)
+        :param sender:
+        '''
+        self.working_start(sender)
+        value = self._get_package_with_attributes(pkg_filter, fields)
+        return self.working_ended(json.dumps(value))
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='sb',
+                                          out_signature='as',
+                                          sender_keyword='sender')
+    def GetPackagesByName(self, name, newest_only, sender=None):
+        '''
+        Get a list of packages from a name pattern
+        :param name: name pattern
+        :param newest_only: True = get newest packages only
+        :param sender:
+        '''
+        self.working_start(sender)
+        pkg_ids = self._get_packages_by_name(name, newest_only)
+        return self.working_ended(pkg_ids)
+
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='ss',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def GetAttribute(self, id, attr,sender=None):
+        '''
+        Get an attribute from a yum package id
+        it will return a python repr string of the attribute
+        :param id: yum package id
+        :param attr: name of attribute (summary, size, description, changelog etc..)
+        :param sender:
+        '''
+        self.working_start(sender)
+        value = self._get_attribute( id, attr)
+        return self.working_ended(value)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='s',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def GetUpdateInfo(self, id,sender=None):
+        '''
+        Get an Update Infomation e from a yum package id
+        it will return a python repr string of the attribute
+        :param id: yum package id
+        :param sender:
+        '''
+        self.working_start(sender)
+        value = self._get_updateInfo(id)
+        return self.working_ended(value)
+
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='i',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def GetHistoryPackages(self, tid,sender=None):
+        '''
+        Get packages from a given yum history transaction id
+
+        :param tid: history transaction id
+        :type tid: integer
+        :return: list of (pkg_id, state, installed) pairs
+        :rtype: json encoded string
+        '''
+        self.working_start(sender)
+        value = json.dumps(self._get_history_transaction_pkgs(tid))
+        return self.working_ended(value)
+
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='ii',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def GetHistoryByDays(self, start_days, end_days ,sender=None):
+        '''
+        Get History transaction in a interval of days from today
+
+        :param start_days: start of interval in days from now (0 = today)
+        :type start_days: integer
+        :param end_days:end of interval in days from now
+        :type end_days: integer
+        :return: a list of (transaction is, date-time) pairs
+        :type sender: json encoded string
+        '''
+        self.working_start(sender)
+        value = json.dumps(self._get_history_by_days(start_days, end_days))
+        return self.working_ended(value)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='as',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def HistorySearch(self, pattern ,sender=None):
+        '''
+        Search the history for transaction matching a pattern
+        :param pattern: patterne to match
+        :type pattern: string
+        :return: list of (tid,isodates)
+        :type sender: json encoded string
+        '''
+        self.working_start(sender)
+        value = json.dumps(self._history_search(pattern))
+        return self.working_ended(value)
+
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='',
+                                          out_signature='b',
+                                          sender_keyword='sender')
+    def Unlock(self, sender=None):
+        ''' release the lock'''
+        self.check_permission(sender)
+        if self.check_lock(sender):
+            self._reset_base()
+            self.logger.info('UNLOCK: Lock Release by %s' % self._lock)
+            self._lock = None
+            return True
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='s',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def Install(self, cmds, sender=None):
+        '''
+        Install packages based on command patterns separated by spaces
+        sinulate what 'yum install <arguments>' does
+        :param cmds: command patterns separated by spaces
+        :param sender:
+        '''
+        self.working_start(sender)
+        value = 0
+        for cmd in cmds.split(' '):
+            if cmd.endswith('.rpm'):
+                self.base.install_local(cmd)
+            else:
+                self.base.install(cmd)
+        value = self._build_transaction()
+        return self.working_ended(value)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='s',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def Remove(self, cmds, sender=None):
+        '''
+        Remove packages based on command patterns separated by spaces
+        sinulate what 'yum remove <arguments>' does
+        :param cmds: command patterns separated by spaces
+        :param sender:
+        '''
+        self.working_start(sender)
+        value = 0
+        for cmd in cmds.split(' '):
+            self.base.remove(cmd)
+        value = self._build_transaction()
+        return self.working_ended(value)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='s',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def Update(self, cmds, sender=None):
+        '''
+        Update packages based on command patterns separated by spaces
+        sinulate what 'yum update <arguments>' does
+        :param cmds: command patterns separated by spaces
+        :param sender:
+        '''
+        self.working_start(sender)
+        value = 0
+        for cmd in cmds.split(' '):
+            self.base.upgrade(cmd)
+        value = self._build_transaction()
+        return self.working_ended(value)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='s',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def Reinstall(self, cmds, sender=None):
+        '''
+        Reinstall packages based on command patterns separated by spaces
+        sinulate what 'yum reinstall <arguments>' does
+        :param cmds: command patterns separated by spaces
+        :param sender:
+        '''
+        self.working_start(sender)
+        value = 0
+        # TODO : Add dnf code (Reinstall)
+        # no Base.reinstall, so we need to use Transaction.add_reinstall()
+        return self.working_ended(value)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='s',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def Downgrade(self, cmds, sender=None):
+        '''
+        Downgrade packages based on command patterns separated by spaces
+        sinulate what 'yum downgrade <arguments>' does
+        :param cmds: command patterns separated by spaces
+        :param sender:
+        '''
+        self.working_start(sender)
+        value = 0
+        for cmd in cmds.split(' '):
+            self.base.downgrade(cmd)
+        value = self._build_transaction()
+        return self.working_ended(value)
+
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='ss',
+                                          out_signature='as',
+                                          sender_keyword='sender')
+
+    def AddTransaction(self, id, action, sender=None):
+        '''
+        Add an package to the current transaction
+
+        :param id: package id for the package to add
+        :param action: the action to perform ( install, update, remove, obsolete, reinstall, downgrade, localinstall )
+        '''
+        self.working_start(sender)
+        value = 0
+        po = self._get_po(id)
+        # TODO : Add dnf code (AddTransaction)
+        # FIXME: missing dnf API of adding to hawkey.Goal object
+        # no easy way to add to the hawkey.Sack object in dnf
+        # using public api
+        # filed upstream bug
+        # https://bugzilla.redhat.com/show_bug.cgi?id=1073859
+        if action == 'install':
+            pass
+        elif action == 'remove':
+            pass
+        elif action == 'update':
+            pass
+        elif action == 'obsolete':
+            pass
+        elif action == 'reinstall':
+            pass
+        elif action == 'downgrade':
+            pass
+        elif action == 'localinstall':
+            pass
+
+
+        return self.working_ended(value)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='',
+                                          out_signature='',
+                                          sender_keyword='sender')
+    def ClearTransaction(self, sender):
+        '''
+        Clear the transactopm
+        '''
+        self.working_start(sender)
+        # TODO : Add dnf code (ClearTransaction)
+        return self.working_ended()
+
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='',
+                                          out_signature='as',
+                                          sender_keyword='sender')
+
+    def GetTransaction(self, sender=None):
+        '''
+        Return the members of the current transaction
+        '''
+        self.working_start(sender)
+        value = []
+        # TODO : Add dnf code (GetTransaction)
+        return self.working_ended(value)
+
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def BuildTransaction(self, sender):
+        '''
+        Resolve dependencies of current transaction
+        '''
+        self.working_start(sender)
+        value = self._build_transaction()
+        return self.working_ended(value)
+
+
+    def _build_transaction(self):
+        '''
+        Resolve dependencies of current transaction
+        '''
+        self.TransactionEvent('start-build',NONE)
+        rc = self.resolve()
+        if rc: # OK
+            output = self._get_transaction_list()
+        else:
+            output = []
+        self.TransactionEvent('end-build',NONE)
+        return json.dumps((rc,output))
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='',
+                                          out_signature='i',
+                                          sender_keyword='sender')
+    def RunTransaction(self, sender = None):
+        '''
+        Run the current yum transaction
+        '''
+        self.working_start(sender)
+        self.check_permission(sender)
+        self.check_lock(sender)
+        self.TransactionEvent('start-run',NONE)
+        self._can_quit = False
+        # TODO : Add dnf code (RunTransaction)
+        self._can_quit = True
+        self._reset_base()
+        self.TransactionEvent('end-run',NONE)
+        return self.working_ended(0)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='asasbbb',
+                                          out_signature='as',
+                                          sender_keyword='sender')
+    def Search(self, fields, keys, match_all, newest_only, tags, sender=None ):
+        '''
+        Search for for packages, where given fields contain given key words
+        :param fields: list of fields to search in
+        :param keys: list of keywords to search for
+        :param match_all: match all flag, if True return only packages matching all keys
+        :param newest_only: return only the newest version of a package
+        :param tags: seach pkgtags
+
+        '''
+        self.working_start(sender)
+        result = self._search(fields, keys, match_all, newest_only, tags)
+        return self.working_ended(result)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='',
+                                          out_signature='s',
+                                          sender_keyword='sender')
+    def GetGroups(self, sender=None ):
+        '''
+        Return a category/group tree
+        '''
+        self.working_start(sender)
+        value = self._get_groups()
+        return self.working_ended(value)
+
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='ss',
+                                          out_signature='as',
+                                          sender_keyword='sender')
+    def GetGroupPackages(self, grp_id, grp_flt, sender=None ):
+        '''
+        Get packages in a group by grp_id and grp_flt
+        :param grp_id: The Group id
+        :param grp_flt: Group Filter (all or default)
+        :param sender:
+        '''
+        self.working_start(sender)
+        pkg_ids = self._get_group_pkgs(grp_id, grp_flt)
+        return self.working_ended(pkg_ids)
+
+    @Logger
+    @dbus.service.method(DAEMON_INTERFACE,
+                                          in_signature='sb',
+                                          out_signature='',
+                                          sender_keyword='sender')
+    def ConfirmGPGImport(self, hexkeyid, confirmed, sender=None ):
+        '''
+        Confirm import of at GPG Key by yum
+        :param hexkeyid: hex keyid for GPG key
+        :param confirmed: confirm import of key (True/False)
+        :param sender:
+        '''
+
+        self.working_start(sender)
+        self._gpg_confirm[hexkeyid] = confirmed # store confirmation of GPG import
+        return self.working_ended()
+
+
+#
+#  Template for new method
+#
+#    @dbus.service.method(DAEMON_INTERFACE,
+#                                          in_signature='',
+#                                          out_signature='',
+#                                          sender_keyword='sender')
+#    def NewMethod(self, sender=None ):
+#        '''
+#
+#        '''
+#        self.working_start(sender)
+#        value = True
+#        return self.working_ended(value)
+#
+
+
+#===============================================================================
+# DBus signals
+#===============================================================================
+    @dbus.service.signal(DAEMON_INTERFACE)
+    def UpdateProgress(self,name,frac,fread,ftime):
+        '''
+        DBus signal with download progress information
+        will send dbus signals with download progress information
+        :param name: filename
+        :param frac: Progress fracment (0 -> 1)
+        :param fread: formated string containing BytesRead
+        :param ftime : formated string containing remaining or elapsed time
+        '''
+        pass
+
+# Parallel Download Progress signals
+
+    @dbus.service.signal(DAEMON_INTERFACE)
+    def DownloadStart(self, num_files, num_bytes):
+        ''' Starting a new parallel download batch '''
+        pass
+
+    @dbus.service.signal(DAEMON_INTERFACE)
+    def DownloadProgress(self, name, frac, total_frac, total_files):
+        ''' Progress for a single instance in the batch '''
+        pass
+
+    @dbus.service.signal(DAEMON_INTERFACE)
+    def DownloadEnd(self, name, status, msg):
+        ''' Download of af single instace ended '''
+        pass
+
+    @dbus.service.signal(DAEMON_INTERFACE)
+    def RepoMetaDataProgress(self, name, frac):
+        ''' Repository Metadata Download progress '''
+
+
+    @dbus.service.signal(DAEMON_INTERFACE)
+    def TransactionEvent(self,event,data):
+        '''
+        DBus signal with Transaction event information, telling the current step in the processing of
+        the current transaction.
+
+        Steps are : start-run, download, pkg-to-download, signature-check, run-test-transaction, run-transaction, fail, end-run
+
+        :param event: current step
+        '''
+        #print "event: %s" % event
+        pass
+
+
+    @dbus.service.signal(DAEMON_INTERFACE)
+    def RPMProgress(self, package, action, te_current, te_total, ts_current, ts_total):
+        """
+        RPM Progress DBus signal
+        :param package: A yum package object or simple string of a package name
+        :param action: A yum.constant transaction set state or in the obscure
+                       rpm repackage case it could be the string 'repackaging'
+        :param te_current: Current number of bytes processed in the transaction
+                           element being processed
+        :param te_total: Total number of bytes in the transaction element being
+                         processed
+        :param ts_current: number of processes completed in whole transaction
+        :param ts_total: total number of processes in the transaction.
+        """
+        pass
+
+
+    @dbus.service.signal(DAEMON_INTERFACE)
+    def GPGImport(self, pkg_id, userid, hexkeyid, keyurl, timestamp ):
+        '''
+        GPG Key Import DBus signal
+
+        :param pkg_id: pkg_id for the package needing the GPG Key to be verified
+        :param userid: GPG key name
+        :param hexkeyid: GPG key hex id
+        :param keyurl: Url to the GPG Key
+        :param timestamp:
+        '''
+        pass
+
+#===============================================================================
+# Helper methods
+#===============================================================================
+    def working_start(self,sender):
+        self.check_permission(sender)
+        self.check_lock(sender)
+        self._is_working = True
+        self._watchdog_count = 0
+
+    def working_ended(self, value=None):
+        self._is_working = False
+        return value
+
+    def handle_gpg_import(self, gpg_info):
+        '''
+        Callback for handling af user confirmation of gpg key import
+
+        :param gpg_info: dict with info about gpg key {"po": ..,  "userid": .., "hexkeyid": .., "keyurl": ..,  "fingerprint": .., "timestamp": ..)
+
+        '''
+        print(gpg_info)
+        pkg_id = self._get_id(gpg_info['po'])
+        userid = gpg_info['userid']
+        hexkeyid = gpg_info['hexkeyid']
+        keyurl = gpg_info['keyurl']
+        fingerprint = gpg_info['fingerprint']
+        timestamp = gpg_info['timestamp']
+        if not hexkeyid in self._gpg_confirm: # the gpg key has not been confirmed by the user
+            self._gpg_confirm[hexkeyid] = False
+            self.GPGImport( pkg_id, userid, hexkeyid, keyurl, timestamp )
+        return self._gpg_confirm[hexkeyid]
+
+
+    def _set_option(self, option, value):
+        # TODO : Add dnf code (_set_option)
+        pass
+
+
+    def _get_history_by_days(self, start, end):
+        '''
+        Get the yum history transaction member located in a date interval from today
+        :param start: start days from today
+        :param end: end days from today
+        '''
+        result = []
+        # TODO : Add dnf code (_get_history_by_days)
+        return self._get_id_time_list(result)
+
+    def _history_search(self, pattern):
+        '''
+        search in yum history
+        :param pattern: list of search patterns
+        :type pattern: list
+        '''
+        result = []
+        # TODO : Add dnf code (_history_search)
+        return self._get_id_time_list(result)
+
+    def _get_history_transaction_pkgs(self, tid):
+        '''
+        return a list of (pkg_id, tx_state, installed_state) pairs from a given
+        yum history transaction id
+        '''
+        result = []
+        # TODO : Add dnf code (_get_history_transaction_pkgs)
+        return result
+
+    def _get_transaction_list(self):
+        '''
+        Generate a list of the current transaction
+        '''
+        out_list = []
+        # TODO : Add dnf code (_get_transaction_list)
+        return out_list
+
+    def _to_transaction_id_list(self, txmbrs):
+        '''
+        return a sorted list of package ids from a list of packages
+        if and po is installed, the installed po id will be returned
+        :param pkgs:
+        '''
+        result = []
+        # TODO : Add dnf code (_to_transaction_id_list)
+        return result
+
+    def check_lock(self, sender):
+        '''
+        Check that the current sender is owning the yum lock
+        :param sender:
+        '''
+        if self._lock == sender:
+            return True
+        else:
+            raise LockedError('dnf is locked by another application')
+
+
+    def check_permission(self, sender):
+        ''' Check for senders permission to run root stuff'''
+        if sender in self.authorized_sender:
+            return
+        else:
+            self._check_permission(sender)
+            self.authorized_sender.add(sender)
+
+
+    def _check_permission(self, sender):
+        '''
+        check senders permissions using PolicyKit1
+        :param sender:
+        '''
+        if not sender: raise ValueError('sender == None')
+
+        obj = dbus.SystemBus().get_object('org.freedesktop.PolicyKit1', '/org/freedesktop/PolicyKit1/Authority')
+        obj = dbus.Interface(obj, 'org.freedesktop.PolicyKit1.Authority')
+        (granted, _, details) = obj.CheckAuthorization(
+                ('system-bus-name', {'name': sender}), DAEMON_ORG, {}, dbus.UInt32(1), '', timeout=600)
+        if not granted:
+            raise AccessDeniedError('Session is not authorized')
+
+
+def main():
+    parser = argparse.ArgumentParser(description='Dnf D-Bus Daemon')
+    parser.add_argument('-v', '--verbose', action='store_true')
+    parser.add_argument('-d', '--debug', action='store_true')
+    parser.add_argument('--notimeout', action='store_true')
+    args = parser.parse_args()
+    if args.verbose:
+        if args.debug:
+            doTextLoggerSetup(logroot='dnfdaemon',loglvl=logging.DEBUG)
+        else:
+            doTextLoggerSetup(logroot='dnfdaemon')
+
+    # setup the DBus mainloop
+    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+    mainloop = gobject.MainLoop()
+    yd = DnfDaemon(mainloop)
+    if not args.notimeout:
+        yd._setup_watchdog()
+    mainloop.run()
+
+if __name__ == '__main__':
+    main()
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..c855a954a1f6ea1566ee863602b77356322c7872
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,157 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/yum-daemon.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/yum-daemon.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/yum-daemon"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/yum-daemon"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
+
+upload:
+	@scp -r _build/html/* timlau.fedorapeople.org:public_html/yumdaemon/.
+
diff --git a/docs/client-python.rst b/docs/client-python.rst
new file mode 100644
index 0000000000000000000000000000000000000000..44871d455833af1964bdef855b44d5a855489bc6
--- /dev/null
+++ b/docs/client-python.rst
@@ -0,0 +1,53 @@
+==========================================
+Client API for Python 2.x and 3.x
+==========================================
+
+.. automodule:: dnfdaemon
+
+Classes
+========
+
+System API
+-------------
+
+.. autoclass:: dnfdaemon.DnfDaemonClient
+    :members: Exit, Lock, Unlock, SetWatchdogState,GetPackageWithAttributes, GetRepositoriesGetRepo, GetConfig, SetConfig,
+    		  GetAttribute, GetUpdateInfo, GetPackages, GetPackagesByName, GetHistoryByDays, HistorySearch, GetHistoryPackages,
+    		  GetGroups, Search, ClearTransaction, GetTransaction, AddTransaction, Install, Remove, Update, Reinstal, Downgrade,
+    		  BuildTransaction, RunTransaction, GetEnabledRepos, GetGroupPackages, ConfirmGPGImport
+    
+Session API
+------------
+
+.. autoclass:: dnfdaemon.DnfDaemonReadOnlyClient
+    :members: Exit, Lock, Unlock, SetWatchdogState,GetPackageWithAttributes, GetRepositoriesGetRepo, GetConfig, 
+    		  GetAttribute, GetUpdateInfo, GetPackages, GetPackagesByName, GetGroups, Search
+    		  BuildTransaction, RunTransaction, GetEnabledRepos, GetGroupPackages
+    
+Exceptions
+============
+
+.. class:: DnfDaemonError(Exception)
+
+Base Exception from the backend
+
+.. class:: AccessDeniedError(DnfDaemonError)
+
+PolicyKit access was denied.
+
+Ex.
+User press cancel button in policykit window
+
+.. class:: LockedError(DnfDaemonError)
+
+dnf is locked by another application
+
+Ex.
+dnf is running in a another session
+You have not called the Lock method to grep the Lock
+
+
+.. class:: YumTransactionError(YumDaemonError)
+
+Error in the yum transaction.
+
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000000000000000000000000000000000000..c4329e79e6309262605b9591bbf2cc81d35c6c50
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,246 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# yum-daemon documentation build configuration file, created by
+# sphinx-quickstart on Sun May 27 11:18:17 2012.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), "../")))
+sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), "../client")))
+
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = 'yum-daemon'
+copyright = '2013, Tim Lauridsen'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.1'
+# The full version, including alpha/beta/rc tags.
+release = '0.1.0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'yum-daemondoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'yum-daemon.tex', 'yum-daemon Documentation',
+   'Tim Lauridsen', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'dnfdaemon', 'dnfdaemon Documentation',
+     ['Tim Lauridsen'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+  ('index', 'ydnfdaemon', 'dnfdaemon Documentation',
+   'Tim Lauridsen', 'dnfdaemon', 'One line description of project.',
+   'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
diff --git a/docs/examples.rst b/docs/examples.rst
new file mode 100644
index 0000000000000000000000000000000000000000..8a05a3d4c86774caba3fa88a61bc7c801758c225
--- /dev/null
+++ b/docs/examples.rst
@@ -0,0 +1,9 @@
+*********
+Examples
+*********
+
+Python 2.x &3.x
+================
+
+
+
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..de6170f65aa68a8500dcf3081e9d2ec04030e302
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,25 @@
+.. dnfaemon documentation master file, created by
+   sphinx-quickstart on Sun May 27 11:18:17 2012.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Welcome to dnfdaemon's documentation!
+======================================
+
+Contents:
+
+.. toctree::
+   :maxdepth: 2
+
+   server
+   client-python
+   examples
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
diff --git a/docs/server.rst b/docs/server.rst
new file mode 100644
index 0000000000000000000000000000000000000000..646f904b4ccc0c8d1b4753b89cc6c915177b2469
--- /dev/null
+++ b/docs/server.rst
@@ -0,0 +1,593 @@
+==========================================
+DBus service API documentation
+==========================================
+
+The dnfdaemon is an easy way to utililize the power of the dnf package manager from your own programs
+
+Data structures
+----------------
+
+.. table:: **Data structures**
+
+   =================================  =================================
+   Name                               Content
+   =================================  =================================
+   Package Id (pkg_id)                "name,epoch,ver,rel,arch,repo_id"
+   Transaction List (tx_list)	      "pkg_id,ts_state"		 
+   =================================  =================================
+   
+|
+   
+.. table:: **Attribute Descriptions**
+
+   ================  =========================================================
+   Attribute         Description
+   ================  =========================================================
+   name              Package Name
+   epoch             Package Epoch
+   ver               Package Version
+   rel               Package Release
+   arch				 Package Architecture
+   repo_id			 Repository Id
+   ts_state			 Transaction Member state
+   ================  =========================================================
+   
+Transaction result::
+
+	<transaction_result> ::= <result>, <result>, ...., <result>
+	<result>             ::= <action>, <pkg_list>
+	<action>             ::= install | update | remove | install-deps | update-deps | remove-deps | skipped
+	<plg_list>           ::= <pkg_info>, <pkg_info>, ......, <pkg_info>
+	<pkg_info>           ::= <pkg_id>, size, <obs_list>
+	<pkg_id>             ::= name, epoch, version, release, arch, repo_id
+	<obs_list>           ::= <obs_id>, <obs_id>, ...., <obs_id>
+	<obs_id>             ::= name, epoch, version, release, arch, repo_id for packages obsoletes by <pkg_id>
+   
+
+==========================================
+System Service
+==========================================
+
+.. table:: **DBus Names**
+
+   ========================  =========================================================
+   Attribute				 Value	
+   ========================  =========================================================
+   object                    org.baseurl.YumSystem
+   interface                 org.baseurl.YumSystem
+   path                      /
+   ========================  =========================================================
+ 
+Misc methods
+-------------
+
+.. function:: GetVersion()
+
+   Get the API version
+
+   :return: string with API version
+
+.. function:: Lock()
+
+   Get the daemon Lock, if posible
+
+.. function:: Unlock()
+
+   Get the daemon Lock, if posible
+
+Repository and config methods
+------------------------------
+
+.. py:function:: GetRepositories(filter)
+
+   Get the value a list of repo ids
+
+   :param filter: filter to limit the listed repositories
+   :type filter: string
+   :return: list of repo id's
+   :rtype: array for stings (as)
+
+.. py:function:: GetRepo(repo_id)
+
+   Get information about a give repo_id
+
+   :param repo_id: repo id 
+   :type repo_id: string
+   :return: a dictionary with repo information **(JSON)**
+   :rtype: string (s)
+
+.. py:function:: SetEnabledRepos(repo_ids):
+
+   Enabled a list of repositories, disabled all other repos
+
+   :param repo_ids: list of repo ids to enable
+
+
+.. py:function:: GetConfig(setting)
+
+   Get the value of a yum config setting
+
+   :param setting: name of setting (debuglevel etc..)
+   :type setting: string
+   :return: the config value of the requested setting **(JSON)**
+   :rtype: string (s)
+
+.. py:function:: SetConfig(setting, value)
+
+   Get the value of a yum config setting
+
+   :param setting: name of setting (debuglevel etc..)
+   :type setting: string
+   :param value: name of setting (debuglevel etc..)
+   :type value: misc types **(JSON)**
+   :return: did the update succed
+   :rtype: boolean (b)
+
+
+Package methods
+----------------
+
+These methods is for getting packages and information about packages
+
+.. function:: GetPackages(pkg_filter)
+
+   get a list of packages matching the filter type
+   
+   :param pkg_filter: package filter ('installed','available','updates','obsoletes','recent','extras')
+   :type pkg_filter: string
+   :return: list of pkg_id's
+   :rtype: array of strings (as)
+   
+
+
+.. function:: GetPackageWithAttributes(pkg_filter, fields)
+
+   | Get a list of pkg list for a given package filter  
+   | each pkg list contains [pkg_id, field,....] where field is a atrribute of the package object  
+   | Ex. summary, size etc.  
+	
+   :param pkg_filter: package filter ('installed','available','updates','obsoletes','recent','extras')
+   :type pkg_filter: string
+   :param fields: yum package objects attributes to get.
+   :type fields: array of strings (as)
+   :return: list of (id, field1, field2...) **(JSON)**, each JSON Sting contains (id, field1, field2...)
+   :rtype: array of strings (as) 
+
+.. py:function:: GetPackagesByName(name, newest_only)
+
+   Get a list of pkg ids for starts with name
+        
+   :param name: name prefix to match
+   :type name: string
+   :param newest_only: show only the newest match or every match.
+   :type newest_only: boolean
+   :return: list of pkg_id's
+   :rtype: array of strings (as)
+
+
+.. py:function:: GetAttribute(id, attr,)
+
+   get yum package attribute (description, filelist, changelog etc)
+
+   :param pkg_id: pkg_id to get attribute from
+   :type pkg_id: string
+   :param attr: name of attribute to get
+   :type attr: string
+   :return: the value of the attribute **(JSON)**, the content depend on attribute being read
+   :rtype:  string (s)
+   
+.. py:function:: GetUpdateInfo(id)
+ 
+   Get Updateinfo for a package
+        
+   :param pkg_id: pkg_id to get update info from
+   :type pkg_id: string
+   :return: update info for the package **(JSON)**
+   :rtype: string (s)
+
+.. py:function:: Search(fields, keys, match_all, newest_only, tags )
+
+   Search for packages where keys is matched in fields
+        
+   :param fields: yum po attributes to search in
+   :type fields: array of strings
+   :param keys: keys to search for
+   :type keys: array of strings
+   :param match_all: match all keys or only one
+   :type match_all: boolean
+   :param newest_only: match all keys or only one
+   :type newest_only: boolean
+   :param tags: match all keys or only one
+   :type tags: boolean
+   :return: list of pkg_id's for matches
+   :rtype: array of stings (as)
+
+
+High level methods
+-------------------
+The high level methods simulate basic dnf command line main functions.
+
+.. py:function:: Install(cmds)
+
+Works just like the ``dnf install <cmds>`` command line
+
+   :param cmds: package arguments separated by spaces
+   :type cmds: string
+   :return: return code, result of resolved transaction (rc = 1 is ok, else failure) **(JSON)**
+   :rtype: string (s)
+
+.. py:function:: Remove(cmds)
+
+   Works just like the ``dnf remove <cmds>`` command line
+
+   :param cmds: package arguments separated by spaces
+   :type cmds: string
+   :return: return code, result of resolved transaction (rc = 1 is ok, else failure) **(JSON)**
+   :rtype: string (s)
+
+
+.. py:function:: Update(cmds)
+
+   Works just like the ``dnf update <cmds>`` command line
+
+   :param cmds: package arguments separated by spaces
+   :type cmds: string
+   :return: return code, result of resolved transaction (rc = 1 is ok, else failure) **(JSON)**
+   :rtype: string (s)
+
+
+.. py:function:: Reinstall(cmds)
+
+   Works just like the ``dnf reinstall <cmds>`` command line
+
+   :param cmds: package arguments separated by spaces
+   :type cmds: string
+   :return: return code, result of resolved transaction (rc = 1 is ok, else failure) **(JSON)**
+   :rtype: string (s)
+
+
+.. py:function:: Downgrade(cmds)
+
+   Works just like the ``dnf downgrade <cmds>`` command line
+
+   :param cmds: package arguments separated by spaces
+   :type cmds: string
+   :return: return code, result of resolved transaction (rc = 1 is ok, else failure) **(JSON)**
+   :rtype: string (s)
+
+
+
+Transaction methods
+--------------------
+These methods is for handling the current yum transaction
+
+.. py:function:: AddTransaction(id, action)
+
+   Add an package to the current transaction 
+        
+   :param id: package id for the package to add
+   :type id: string
+   :param action: the action to perform ( install, update, remove, obsolete, reinstall, downgrade, localinstall )
+   :type action: string
+   :return: list of (pkg_id, transaction state) pairs for the added members (comma separated)
+   :rtype: array of strings (as)
+
+.. py:function:: ClearTransaction()
+
+   Clear the current transaction
+   
+.. py:function:: GetTransaction()
+
+   Get the currrent transaction
+
+   :return: list of (pkg_id, transaction state) pairs in the current transaction (comma separated)
+   :rtype: array of strings (as)
+   
+.. py:function:: BuildTransaction()
+
+   Depsolve the current transaction
+   
+   :return: (return code, result of resolved transaction) pair (rc = 1 is ok, else failure) **(JSON)**
+   :rtype: string (s)
+   
+	
+.. py:function:: RunTransaction()
+
+   Execute the current transaction
+   
+   :return: state of run transaction (0 = ok, 1 = need GPG import confirmation, 2 = error)
+   :rtype: int (i)
+
+.. py:function:: ConfirmGPGImport(self, hexkeyid, confirmed)
+
+   Confirm import of at GPG Key by yum
+   
+   :param hexkeyid: hex keyid for GPG key
+   :type hexkeyid: string (s)
+   :param confirmed: confirm import of key (True/False)
+   :type confirmed: boolean (b)
+   
+
+Groups
+-------
+
+Methods to work with yum groups and categories
+
+.. py:function:: GetGroups( )
+
+   Get available Categories & Groups
+
+.. py:function:: GetGroupPackages(grp_id, grp_flt )
+
+   Get packages in a group by grp_id and grp_flt
+    
+   :param grp_id: The Group id
+   :type grp_id: string (s)
+   :param grp_flt: Group Filter (all or default)
+   :type grp_flt: string (s)
+   :return: list of pkg_id's
+   :rtype: array of strings (as)
+    
+
+.. note:: Under Development
+   
+   More to come in the future, methods to install groups etc. has to be defined and implemented
+
+History
+--------
+
+Methods to work with the package transaction history
+
+.. py:function:: GetHistoryByDays(start_days, end_days)
+
+        Get History transaction in a interval of days from today
+        
+        :param start_days: start of interval in days from now (0 = today)
+        :type start_days: integer
+        :param end_days: end of interval in days from now
+        :type end_days: integer
+        :return: a list of (transaction ids, date-time) pairs (JSON)
+		:rtype: string (s)
+
+.. py:function:: GetHistoryPackages(tid)
+
+        Get packages from a given yum history transaction id
+        
+        :param tid: history transaction id
+        :type tid: integer
+        :return: list of (pkg_id, state, installed) pairs
+        :rtype: json encoded string
+
+.. py:function:: HistorySearch(pattern)
+
+        Search the history for transaction matching a pattern
+
+        :param pattern: patterne to match
+        :type pattern: string
+        :return: list of (tid,isodates)
+        :type sender: json encoded string
+
+Signals
+--------
+
+.. py:function:: TransactionEvent(self,event,data):
+
+        Signal with Transaction event information, telling the current step in the processing of
+        the current transaction.
+        
+        Steps are : start-run, download, pkg-to-download, signature-check, run-test-transaction, run-transaction, fail, end-run
+        
+        :param event: current step 
+
+
+.. py:function:: RPMProgress(self, package, action, te_current, te_total, ts_current, ts_total):
+        
+        signal with RPM Progress
+        
+        :param package: A yum package object or simple string of a package name
+        :param action: A yum.constant transaction set state or in the obscure
+                       rpm repackage case it could be the string 'repackaging'
+        :param te_current: Current number of bytes processed in the transaction
+                           element being processed
+        :param te_total: Total number of bytes in the transaction element being
+                         processed
+        :param ts_current: number of processes completed in whole transaction
+        :param ts_total: total number of processes in the transaction.
+
+
+.. py:function:: GPGImport(self, pkg_id, userid, hexkeyid, keyurl, timestamp ):
+
+        signal with GPG Key information of a key there need to be confirmed to complete the 
+        current transaction. after signal is send transaction will abort with rc=1
+        Use ConfirmGPGImport method to comfirm the key and run RunTransaction again 
+        
+        
+        :param pkg_id: pkg_id for the package needing the GPG Key to be verified
+        :param userid: GPG key name
+        :param hexkeyid: GPG key hex id
+        :param keyurl: Url to the GPG Key
+        :param timestamp: 
+        '''
+
+.. note:: Under Development
+   
+   The progress signals for download progress is not documented yet
+
+   
+==========================================
+Session Service
+==========================================
+.. table:: **DBus Names**
+
+   ========================  =========================================================
+   Attribute				 Value	
+   ========================  =========================================================
+   object                    org.baseurl.YumSession
+   interface                 org.baseurl.YumSession
+   path                      /
+   ========================  =========================================================
+
+
+ 
+Misc methods
+-------------
+
+.. function:: GetVersion()
+
+   Get the API version
+
+   :return: string with API version
+
+.. function:: Lock()
+
+   Get the daemon Lock, if posible
+
+.. function:: Unlock()
+
+   Get the daemon Lock, if posible
+
+Repository and config methods
+------------------------------
+
+.. py:function:: GetRepositories(filter)
+
+   Get the value a list of repo ids
+
+   :param filter: filter to limit the listed repositories
+   :type filter: string
+   :return: list of repo id's
+   :rtype: array for stings (as)
+
+.. py:function:: GetRepo(repo_id)
+
+   Get information about a give repo_id
+
+   :param repo_id: repo id 
+   :type repo_id: string
+   :return: a dictionary with repo information **(JSON)**
+   :rtype: string (s)
+
+.. py:function:: SetEnabledRepos(repo_ids):
+
+   Enabled a list of repositories, disabled all other repos
+
+   :param repo_ids: list of repo ids to enable
+
+.. py:function:: GetConfig(setting)
+
+   Get the value of a yum config setting
+
+   :param setting: name of setting (debuglevel etc..)
+   :type setting: string
+   :return: the config value of the requested setting **(JSON)**
+   :rtype: string (s)
+
+Package methods
+----------------
+
+These methods is for getting packages and information about packages
+
+.. function:: GetPackages(pkg_filter)
+
+   get a list of packages matching the filter type
+   
+   :param pkg_filter: package filter ('installed','available','updates','obsoletes','recent','extras')
+   :type pkg_filter: string
+   :return: list of pkg_id's
+   :rtype: array of strings (as)
+   
+
+
+.. function:: GetPackageWithAttributes(pkg_filter, fields)
+
+   | Get a list of pkg list for a given package filter  
+   | each pkg list contains [pkg_id, field,....] where field is a atrribute of the package object  
+   | Ex. summary, size etc.  
+	
+   :param pkg_filter: package filter ('installed','available','updates','obsoletes','recent','extras')
+   :type pkg_filter: string
+   :param fields: yum package objects attributes to get.
+   :type fields: array of strings (as)
+   :return: list of (id, field1, field2...) **(JSON)**, each JSON Sting contains (id, field1, field2...)
+   :rtype: array of strings (as) 
+
+.. py:function:: GetPackagesByName(name, newest_only)
+
+   Get a list of pkg ids for starts with name
+        
+   :param name: name prefix to match
+   :type name: string
+   :param newest_only: show only the newest match or every match.
+   :type newest_only: boolean
+   :return: list of pkg_id's
+   :rtype: array of strings (as)
+
+
+.. py:function:: GetAttribute(id, attr,)
+
+   get yum package attribute (description, filelist, changelog etc)
+
+   :param pkg_id: pkg_id to get attribute from
+   :type pkg_id: string
+   :param attr: name of attribute to get
+   :type attr: string
+   :return: the value of the attribute **(JSON)**, the content depend on attribute being read
+   :rtype:  string (s)
+   
+.. py:function:: GetUpdateInfo(id)
+ 
+   Get Updateinfo for a package
+        
+   :param pkg_id: pkg_id to get update info from
+   :type pkg_id: string
+   :return: update info for the package **(JSON)**
+   :rtype: string (s)
+
+.. py:function:: Search(fields, keys, match_all, newest_only, tags )
+
+   Search for packages where keys is matched in fields
+        
+   :param fields: yum po attributes to search in
+   :type fields: array of strings
+   :param keys: keys to search for
+   :type keys: array of strings
+   :param match_all: match all keys or only one
+   :type match_all: boolean
+   :param newest_only: match all keys or only one
+   :type newest_only: boolean
+   :param tags: search in pkgtags
+   :type tags: boolean   
+   :return: list of pkg_id's for matches
+   :rtype: array of stings (as)
+
+
+Groups
+-------
+
+Methods to work with dnf groups and categories
+
+.. py:function:: GetGroups( )
+
+   Get available Categories & Groups
+
+.. py:function:: GetGroupPackages(grp_id, grp_flt )
+
+   Get packages in a group by grp_id and grp_flt
+    
+   :param grp_id: The Group id
+   :type grp_id: string (s)
+   :param grp_flt: Group Filter (all or default)
+   :type grp_flt: string (s)
+   :return: list of pkg_id's
+   :rtype: array of strings (as)
+
+.. note:: Under Development
+   
+   More to come in the future, methods to install groups etc. has to be defined and implemented
+
+Signals
+--------
+
+.. note:: Under Development
+   
+   Signals is not documented yet
diff --git a/policykit1/org.baseurl.DnfSystem.policy b/policykit1/org.baseurl.DnfSystem.policy
new file mode 100644
index 0000000000000000000000000000000000000000..b57839b13c8869ea68fcd2b24414ac756849f46a
--- /dev/null
+++ b/policykit1/org.baseurl.DnfSystem.policy
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE policyconfig PUBLIC
+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
+<policyconfig>
+  <vendor>Yum</vendor>
+  <vendor_url>http://dnf.baseurl.org/</vendor_url>
+  <action id="org.baseurl.DnfSystem">
+    <description>dnf</description>
+    <message>Application requesting dnf to search/modify system packages</message>
+    <defaults>
+      <allow_inactive>no</allow_inactive>
+      <allow_active>auth_admin_keep</allow_active>
+    </defaults>
+  </action>
+</policyconfig>
diff --git a/test/base.py b/test/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..361a43652eff30aef9c300dcda4ecc94ffce838e
--- /dev/null
+++ b/test/base.py
@@ -0,0 +1,262 @@
+import sys
+import os.path
+sys.path.insert(0,os.path.abspath('client'))
+import unittest
+from datetime import date
+from dnfdaemon import DnfDaemonClient, DnfDaemonReadOnlyClient
+
+class TestBase(unittest.TestCase, DnfDaemonClient):
+    def __init__(self, methodName='runTest'):
+        unittest.TestCase.__init__(self, methodName)
+        DnfDaemonClient.__init__(self)
+        self._gpg_confirm = None
+        self._signals = []
+
+    def setUp(self):
+        self.Lock()
+
+    def tearDown(self):
+        self.Unlock()
+
+    def reset_signals(self):
+        self._signals = []
+
+    def check_signal(self, name):
+        if name in self._signals:
+            return True
+        else:
+            return False
+
+    def show_changelog(self, changelog, max_elem=3):
+        i = 0
+        for (c_date, c_ver, msg) in changelog:
+            i += 1
+            if i > max_elem:
+                return
+            print("* %s %s" % (date.fromtimestamp(c_date).isoformat(), c_ver))
+            for line in msg.split('\n'):
+                print("%s" % line)
+
+    def show_package_list(self, pkgs):
+        for pkg_id in pkgs:
+            (n, e, v, r, a, repo_id) = self.to_pkg_tuple(pkg_id)
+            print " --> %s-%s:%s-%s.%s (%s)" % (n, e, v, r, a, repo_id)
+
+    def show_transaction_list(self, pkgs):
+        for pkg_id in pkgs:
+            pkg_id = str(pkg_id)
+            (n, e, v, r, a, repo_id, ts_state) = self.to_txmbr_tuple(pkg_id)
+            print " --> %s-%s:%s-%s.%s (%s) - %s" % (n, e, v, r, a, repo_id, ts_state)
+
+    def show_transaction_result(self, output):
+        for action, pkgs in output:
+            print "  %s" % action
+            for pkg in pkgs:
+                print "  --> %s" % str(pkg)
+
+# ======================== Helpers =======================
+    def _add_to_transaction(self, name):
+        '''
+        Helper to add a package to transaction
+        '''
+        pkgs = self.GetPackagesByName(name, newest_only=True)
+        # pkgs should be a list instance
+        self.assertIsInstance(pkgs, list)
+        self.assertEqual(len(pkgs),1)
+        pkg = pkgs[0]
+        (n, e, v, r, a, repo_id) = self.to_pkg_tuple(pkg)
+        if repo_id[0] == '@':
+            action='remove'
+        else:
+            action='install'
+        txmbrs = self.AddTransaction(pkg,action)
+        self.assertIsInstance(txmbrs, list)
+        return txmbrs
+
+    def _run_transaction(self, build=True):
+        '''
+        Desolve deps and run the current transaction
+        '''
+        print('************** Running the current transaction *********************')
+        if build:
+            rc, output = self.BuildTransaction()
+            self.assertEqual(rc,2)
+            self.show_transaction_result(output)
+            self.assertGreater(len(output),0)
+        self.RunTransaction()
+
+    def check_installed(self, name):
+        pkgs = self.GetPackagesByName(name, newest_only=True)
+        # pkgs should be a list instance
+        for pkg in pkgs:
+            (n, e, v, r, a, repo_id) = self.to_pkg_tuple(pkg)
+            if repo_id[0] == '@':
+                return True
+        return False
+
+    def _is_installed(self, name):
+        pkgs = self.GetPackagesByName(name, newest_only=True)
+        # pkgs should be a list instance
+        self.assertIsInstance(pkgs, list)
+        self.assertTrue(len(pkgs)>0)
+        for pkg in pkgs:
+            (n, e, v, r, a, repo_id) = self.to_pkg_tuple(pkg)
+            if repo_id[0] == '@':
+                return True
+        return False
+
+    def _show_package(self, id):
+        (n, e, v, r, a, repo_id) = self.to_pkg_tuple(id)
+        print "\nPackage attributes"
+        self.assertIsInstance(n, str)
+        print "Name : %s " % n
+        summary = self.GetAttribute(id, 'summary')
+        self.assertIsInstance(summary, unicode)
+        print "Summary : %s" % summary
+        print "\nDescription:"
+        desc = self.GetAttribute(id, 'description')
+        self.assertIsInstance(desc, unicode)
+        print desc
+#         print "\nChangelog:"
+#         changelog = self.GetAttribute(id, 'changelog')
+#         self.assertIsInstance(changelog, list)
+#         self.show_changelog(changelog, max_elem=2)
+        # Check a not existing attribute dont make it blow up
+        notfound = self.GetAttribute(id, 'notfound')
+        self.assertIsNone(notfound)
+        print " Value of attribute 'notfound' : %s" % notfound
+
+###############################################################################
+# Dbus Signal Handlers
+###############################################################################
+
+    def on_UpdateProgress(self,name,frac,fread,ftime):
+        self._signals.append("UpdateProgress")
+        pass
+
+    def on_TransactionEvent(self,event, data):
+        self._signals.append("TransactionEvent")
+        pass
+
+    def on_RPMProgress(self, package, action, te_current, te_total, ts_current, ts_total):
+        self._signals.append("RPMProgress")
+        pass
+
+    def on_GPGImport(self, pkg_id, userid, hexkeyid, keyurl, timestamp ):
+        self._signals.append("GPGImport")
+        values =  (pkg_id, userid, hexkeyid, keyurl, timestamp)
+        self._gpg_confirm = values
+        print "received signal : GPGImport%s" % (repr(values))
+
+    def on_DownloadStart(self, num_files, num_bytes):
+        ''' Starting a new parallel download batch '''
+        values =  (num_files, num_bytes)
+        print("on_DownloadStart : %s" % (repr(values)))
+
+    def on_DownloadProgress(self, name, frac, total_frac, total_files):
+        ''' Progress for a single instance in the batch '''
+        values =  (name, frac, total_frac, total_files)
+        print("on_DownloadProgress : %s" % (repr(values)))
+
+    def on_DownloadEnd(self, name, status, msg):
+        ''' Download of af single instace ended '''
+        values =  (name, status, msg)
+        print("on_DownloadEnd : %s" % (repr(values)))
+
+    def on_RepoMetaDataProgress(self, name, frac):
+        ''' Repository Metadata Download progress '''
+        values =  (name, frac)
+        print("on_RepoMetaDataProgress : %s" % (repr(values)))
+
+
+class TestBaseReadonly(unittest.TestCase, DnfDaemonReadOnlyClient):
+    def __init__(self, methodName='runTest'):
+        unittest.TestCase.__init__(self, methodName)
+        DnfDaemonReadOnlyClient.__init__(self)
+
+    def setUp(self):
+        self.Lock()
+
+    def tearDown(self):
+        self.Unlock()
+
+    def show_changelog(self, changelog, max_elem=3):
+        i = 0
+        for (c_date, c_ver, msg) in changelog:
+            i += 1
+            if i > max_elem:
+                return
+            print("* %s %s" % (date.fromtimestamp(c_date).isoformat(), c_ver))
+            for line in msg.split('\n'):
+                print("%s" % line)
+
+    def show_package_list(self, pkgs):
+        for pkg_id in pkgs:
+            (n, e, v, r, a, repo_id) = self.to_pkg_tuple(pkg_id)
+            print " --> %s-%s:%s-%s.%s (%s)" % (n, e, v, r, a, repo_id)
+
+
+    def _is_installed(self, name):
+        pkgs = self.GetPackagesByName(name, newest_only=True)
+        # pkgs should be a list instance
+        self.assertIsInstance(pkgs, list)
+        self.assertTrue(len(pkgs)>0)
+        for pkg in pkgs:
+            (n, e, v, r, a, repo_id) = self.to_pkg_tuple(pkg)
+            if repo_id[0] == '@':
+                return True
+        return False
+
+    def _show_package(self, id):
+        (n, e, v, r, a, repo_id) = self.to_pkg_tuple(id)
+        print "\nPackage attributes"
+        self.assertIsInstance(n, str)
+        print "Name : %s " % n
+        summary = self.GetAttribute(id, 'summary')
+        self.assertIsInstance(summary, unicode)
+        print "Summary : %s" % summary
+        print "\nDescription:"
+        desc = self.GetAttribute(id, 'description')
+        self.assertIsInstance(desc, unicode)
+        print desc
+#         print "\nChangelog:"
+#         changelog = self.GetAttribute(id, 'changelog')
+#         self.assertIsInstance(changelog, list)
+#         self.show_changelog(changelog, max_elem=2)
+        # Check a not existing attribute dont make it blow up
+        notfound = self.GetAttribute(id, 'notfound')
+        self.assertIsNone(notfound)
+        print " Value of attribute 'notfound' : %s" % notfound
+
+###############################################################################
+# Dbus Signal Handlers
+###############################################################################
+
+    def on_UpdateProgress(self,name,frac,fread,ftime):
+        pass
+
+    def on_TransactionEvent(self,event, data):
+        pass
+
+    def on_RPMProgress(self, package, action, te_current, te_total, ts_current, ts_total):
+        pass
+
+    def on_DownloadStart(self, num_files, num_bytes):
+        ''' Starting a new parallel download batch '''
+        values =  (num_files, num_bytes)
+        print("on_DownloadStart : %s" % (repr(values)))
+
+    def on_DownloadProgress(self, name, frac, total_frac, total_files):
+        ''' Progress for a single instance in the batch '''
+        values =  (name, frac, total_frac, total_files)
+        print("on_DownloadProgress : %s" % (repr(values)))
+
+    def on_DownloadEnd(self, name, status, msg):
+        ''' Download of af single instace ended '''
+        values =  (name, status, msg)
+        print("on_DownloadEnd : %s" % (repr(values)))
+
+    def on_RepoMetaDataProgress(self, name, frac):
+        ''' Repository Metadata Download progress '''
+        values =  (name, frac)
+        print("on_RepoMetaDataProgress : %s" % (repr(values)))
diff --git a/test/test-session-api.py b/test/test-session-api.py
new file mode 100644
index 0000000000000000000000000000000000000000..81f9355bd99a6671dda2c2535560542f9c4e6edc
--- /dev/null
+++ b/test/test-session-api.py
@@ -0,0 +1,251 @@
+import sys, os
+sys.path.insert(0,os.path.abspath('client'))
+#from base import TestBaseReadonly as TestBase
+from base import TestBase, TestBaseReadonly
+from dnfdaemon import LockedError
+from subprocess import check_output, call
+from nose.exc import SkipTest
+import time
+
+"""
+This module is used for testing new unit tests
+When the test method is completted it is move som test-api.py
+
+use 'nosetest -v -s unit-devel.py' to run the tests
+"""
+
+class TestAPIDevel(TestBaseReadonly):
+
+    def __init__(self, methodName='runTest'):
+        TestBaseReadonly.__init__(self, methodName)
+
+    def test_Locking(self):
+        '''
+        Session: Unlock and Lock
+        '''
+        print
+        # release the lock (grabbed by setUp)
+        self.Unlock()
+        # calling a method without a lock should raise a YumLockedError
+        # self.assertRaises(YumLockedError,self.Install, '0xFFFF')
+        # trying to unlock method without a lock should raise a YumLockedError
+        self.assertRaises(LockedError,self.Unlock)
+        # get the Lock again, else tearDown will fail
+        self.Lock()
+
+    def test_GetPackages(self):
+        '''
+        Session: GetPackages
+        '''
+        print
+        for narrow in ['installed','available']:
+            print(' Getting packages : %s' % narrow)
+            pkgs = self.GetPackages(narrow)
+            self.assertIsInstance(pkgs, list)
+            self.assertGreater(len(pkgs),0) # the should be more than once
+            print('  packages found : %s ' % len(pkgs))
+            pkg_id = pkgs[-1] # last pkg in list
+            print(pkg_id)
+            self._show_package(pkg_id)
+        for narrow in ['updates','obsoletes','recent','extras']:
+            print('  ==================== Getting packages : %s =============================' % narrow)
+            pkgs = self.GetPackages(narrow)
+            self.assertIsInstance(pkgs, list)
+            print('  packages found : %s ' % len(pkgs))
+            if len(pkgs) > 0:
+                pkg_id = pkgs[0] # last pkg in list
+                print(pkg_id)
+                self._show_package(pkg_id)
+        for narrow in ['notfound']: # Dont exist, but it should not blow up
+            print(' Getting packages : %s' % narrow)
+            pkgs = self.GetPackages(narrow)
+            self.assertIsInstance(pkgs, list)
+            self.assertEqual(len(pkgs),0) # the should be notting
+            print('  packages found : %s ' % len(pkgs))
+
+    def test_GetPackagesByName(self):
+        '''
+        Session: GetPackagesByName
+        '''
+        print
+        print "Get all available versions of yum"
+        pkgs = self.GetPackagesByName('yum', newest_only=False)
+        # pkgs should be a list instance
+        self.assertIsInstance(pkgs, list)
+        num1 = len(pkgs)
+        self.assertNotEqual(num1, 0) # yum should always be there
+        for pkg in pkgs:
+            print "  Package : %s" % pkg
+            (n, e, v, r, a, repo_id) = self.to_pkg_tuple(pkg)
+            self.assertEqual(n,"yum")
+        print "Get newest versions of yum"
+        pkgs = self.GetPackagesByName('yum', newest_only=True)
+        # pkgs should be a list instance
+        self.assertIsInstance(pkgs, list)
+        num2 = len(pkgs)
+        self.assertEqual(num2, 1) # there can only be one :)
+        for pkg in pkgs:
+            print "  Package : %s" % pkg
+            (n, e, v, r, a, repo_id) = self.to_pkg_tuple(pkg)
+            self.assertEqual(n,"yum")
+        print "Get the newest packages starting with yum-plugin-"
+        pkgs = self.GetPackagesByName('yum-plugin-*', newest_only=True)
+        # pkgs should be a list instance
+        self.assertIsInstance(pkgs, list)
+        num3 = len(pkgs)
+        self.assertGreater(num3, 1) # there should be more than one :)
+        for pkg in pkgs:
+            print "  Package : %s" % pkg
+            (n, e, v, r, a, repo_id) = self.to_pkg_tuple(pkg)
+            self.assertTrue(n.startswith('yum'))
+
+    def test_Repositories(self):
+        '''
+        Session: GetRepository and GetRepo
+        '''
+        print
+        print("  Getting enabled repos")
+        repos = self.GetRepositories('')
+        self.assertIsInstance(repos, list)
+        for repo_id in repos:
+            print("    Repo : %s" % repo_id)
+        print "  Getting *-source repos"
+        repos = self.GetRepositories('*-source')
+        self.assertIsInstance(repos, list)
+        for repo_id in repos:
+            print("    Repo : %s" % repo_id)
+            self.assertTrue(repo_id.endswith('-source'))
+        print("  \nGetting fedora repository")
+        repo = self.GetRepo('updates')
+        self.assertIsInstance(repo, dict)
+        print("  Repo: fedora")
+        print("  Name : %s " % repo['name'])
+        print("  Metalink :\n  %s " % repo['metalink'])
+        print("  enabled : %s " % repo['enabled'])
+        print("  gpgcheck : %s " % repo['gpgcheck'])
+
+        # check for a repo not there
+        repo = self.GetRepo('XYZCYZ')
+        self.assertIsNone(repo)
+
+
+    def test_Search(self):
+        '''
+        Session: Search
+        '''
+        fields = ['name','summary']
+        keys = ['yum','plugin']
+        pkgs = self.Search(fields, keys ,True,True,False)
+        self.assertIsInstance(pkgs, list)
+        for p in pkgs:
+            summary = self.GetAttribute(p,'summary')
+            print str(p),summary
+            self.assertTrue(keys[0] in str(p) or keys[0] in summary)
+            self.assertTrue(keys[1] in str(p) or keys[1] in summary)
+        keys = ['yum','zzzzddddsss'] # second key should not be found
+        pkgs = self.Search(fields, keys ,True,True, False)
+        self.assertIsInstance(pkgs, list)
+        print "found %i packages" % len(pkgs)
+        self.assertEqual(len(pkgs), 0) # when should not find any matches
+        keys = ['yum','zzzzddddsss'] # second key should not be found
+        pkgs = self.Search(fields, keys ,False, True, False)
+        self.assertIsInstance(pkgs, list)
+        print "found %i packages" % len(pkgs)
+        self.assertGreater(len(pkgs), 0) # we should find some matches
+        # retro should match some pkgtags
+        keys = ['retro'] # second key should not be found
+        pkgs = self.Search(fields, keys ,True, True, True)
+        self.assertIsInstance(pkgs, list)
+        print "found %i packages" % len(pkgs)
+        self.assertGreater(len(pkgs), 0) # we should find some matches
+
+    def test_PackageActions(self):
+        """
+        Session: GetPackageWithAttributes & GetAttribute (action)
+        """
+        print()
+        flt_dict = {'installed':['remove'],'updates':['update'],'obsoletes':['obsolete'], 'available':['install','remove','update','obsolete']}
+        for flt in flt_dict.keys():
+            now = time.time()
+            result = self.GetPackageWithAttributes(flt, ['summary','size'])
+            print("%s, # = %s, time = %.3f" % (flt, len(result),time.time()-now))
+            self.assertIsInstance(result, list) # result is a list
+            i = 0
+            for elem in result:
+                self.assertIsInstance(elem, list) # each elem is a list
+                self.assertEqual(len(elem),3) # 3 elements
+                i += 1
+                if i > 10: break # only test the first 10 elements
+                now = time.time()
+                action = self.GetAttribute(elem[0], 'action')
+                name =  elem[0].split(",")[0]
+                print("    %s = %s , time = %.3f" % (name, action, time.time()-now))
+                self.assertIn(action, flt_dict[flt])
+
+
+    def test_Groups(self):
+        """
+        Session: Groups (GetGroups & GetGroupPackages)
+        """
+
+        result = self.GetGroups()
+        for cat, grps in result:
+            # cat: [category_id, category_name, category_desc]
+            self.assertIsInstance(cat, list) # cat is a list
+            self.assertIsInstance(grps, list) # grps is a list
+            self.assertEqual(len(cat),3) # cat has 3 elements
+            print " --> %s" % cat[0]
+            for grp in grps:
+                # [group_id, group_name, group_desc, group_is_installed]
+                self.assertIsInstance(grp, list) # grp is a list
+                self.assertEqual(len(grp),4) # grp has 4 elements
+                print "   tag: %s name: %s \n       desc: %s \n       installed : %s " % tuple(grp)
+                # Test GetGroupPackages
+                grp_id = grp[0]
+                pkgs = self.GetGroupPackages(grp_id,'all')
+                self.assertIsInstance(pkgs, list) # cat is a list
+                print "       # of Packages in group         : ",len(pkgs)
+                pkgs = self.GetGroupPackages(grp_id,'default')
+                self.assertIsInstance(pkgs, list) # cat is a list
+                print "       # of Default Packages in group : ",len(pkgs)
+
+    def test_Downgrades(self):
+        '''
+        Session: GetAttribute( downgrades )
+        '''
+        print "Get newest versions of yum"
+        pkgs = self.GetPackagesByName('yum', newest_only=True)
+        # pkgs should be a list instance
+        self.assertIsInstance(pkgs, list)
+        num2 = len(pkgs)
+        self.assertEqual(num2, 1) # there can only be one :)
+        downgrades = self.GetAttribute(pkgs[0], 'downgrades')
+        self.assertIsInstance(downgrades, list)
+        (n, e, v, r, a, repo_id) = self.to_pkg_tuple(pkgs[0])
+        inst_evr = "%s:%s.%s" % (e,v,r)
+        print("Installed : %s" % pkgs[0])
+        for id in downgrades:
+            (n, e, v, r, a, repo_id) = self.to_pkg_tuple(id)
+            evr = "%s:%s.%s" % (e,v,r)
+            self.assertTrue(evr < inst_evr)
+            print("  Downgrade : %s" % id)
+
+    def test_GetConfig(self):
+        '''
+        Session: GetConfig
+        '''
+        all_conf = self.GetConfig('*')
+        self.assertIsInstance(all_conf, dict)
+        for key in all_conf:
+            print "   %s = %s" % (key,all_conf[key])
+        fastestmirror = self.GetConfig('fastestmirror')
+        print("fastestmirror : %s" % fastestmirror)
+        self.assertIn(fastestmirror, [True,False])
+        not_found = self.GetConfig('not_found')
+        print("not_found : %s" % not_found)
+        self.assertIsNone(not_found)
+
+
+
+
+
diff --git a/test/unit-devel.py b/test/unit-devel.py
new file mode 100644
index 0000000000000000000000000000000000000000..38ee8ae808160f6bea2b3e22363c48aee8d564c6
--- /dev/null
+++ b/test/unit-devel.py
@@ -0,0 +1,33 @@
+import sys, os
+sys.path.insert(0,os.path.abspath('client'))
+#from base import TestBaseReadonly as TestBase
+from base import TestBase, TestBaseReadonly
+from dnfdaemon import LockedError
+from subprocess import check_output, call
+from nose.exc import SkipTest
+
+"""
+This module is used for testing new unit tests
+When the test method is completted it is move som test-api.py
+
+use 'nosetest -v -s unit-devel.py' to run the tests
+"""
+
+class TestAPIDevel(TestBaseReadonly):
+
+    def __init__(self, methodName='runTest'):
+        TestBaseReadonly.__init__(self, methodName)
+
+    def test_Locking(self):
+        '''
+        Session: Unlock and Lock
+        '''
+        print
+        # release the lock (grabbed by setUp)
+        self.Unlock()
+        # calling a method without a lock should raise a YumLockedError
+        # self.assertRaises(YumLockedError,self.Install, '0xFFFF')
+        # trying to unlock method without a lock should raise a YumLockedError
+        self.assertRaises(LockedError,self.Unlock)
+        # get the Lock again, else tearDown will fail
+        self.Lock()
diff --git a/tools/git2cl b/tools/git2cl
new file mode 100755
index 0000000000000000000000000000000000000000..aa1e8c18d764e8976f4ebbe9a96668f21bd5aa05
--- /dev/null
+++ b/tools/git2cl
@@ -0,0 +1,308 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2007 Simon Josefsson.
+#
+# The functions mywrap, last_line_len, wrap_log_entry are derived from
+# the cvs2cl tool, see <http://www.red-bean.com/cvs2cl/>:
+# Copyright (C) 2001,2002,2003,2004 Martyn J. Pearce <fluffy@cpan.org>
+# Copyright (C) 1999 Karl Fogel <kfogel@red-bean.com>
+#
+# git2cl is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# git2cl 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 git2cl; see the file COPYING.  If not, write to the Free
+# Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA.
+
+use strict;
+use Date::Parse qw(strptime);
+use POSIX qw(strftime);
+use Text::Wrap qw(wrap);
+
+use constant EMPTY_LOG_MESSAGE => '*** empty log message ***';
+
+sub mywrap {
+    my ($indent1, $indent2, @text) = @_;
+    # If incoming text looks preformatted, don't get clever
+    my $text = Text::Wrap::wrap($indent1, $indent2, @text);
+    if ( grep /^\s+/m, @text ) {
+	return $text;
+    }
+    my @lines = split /\n/, $text;
+    $indent2 =~ s!^((?: {8})+)!"\t" x (length($1)/8)!e;
+    $lines[0] =~ s/^$indent1\s+/$indent1/;
+    s/^$indent2\s+/$indent2/
+	for @lines[1..$#lines];
+    my $newtext = join "\n", @lines;
+    $newtext .= "\n"
+	if substr($text, -1) eq "\n";
+    return $newtext;
+}
+
+sub last_line_len {
+    my $files_list = shift;
+    my @lines = split (/\n/, $files_list);
+    my $last_line = pop (@lines);
+    return length ($last_line);
+}
+
+# A custom wrap function, sensitive to some common constructs used in
+# log entries.
+sub wrap_log_entry {
+    my $text = shift;                  # The text to wrap.
+    my $left_pad_str = shift;          # String to pad with on the left.
+
+    # These do NOT take left_pad_str into account:
+    my $length_remaining = shift;      # Amount left on current line.
+    my $max_line_length  = shift;      # Amount left for a blank line.
+
+    my $wrapped_text = '';             # The accumulating wrapped entry.
+    my $user_indent = '';              # Inherited user_indent from prev line.
+
+    my $first_time = 1;                # First iteration of the loop?
+    my $suppress_line_start_match = 0; # Set to disable line start checks.
+
+    my @lines = split (/\n/, $text);
+    while (@lines)   # Don't use `foreach' here, it won't work.
+    {
+	my $this_line = shift (@lines);
+	chomp $this_line;
+
+	if ($this_line =~ /^(\s+)/) {
+	    $user_indent = $1;
+	}
+	else {
+	    $user_indent = '';
+	}
+
+	# If it matches any of the line-start regexps, print a newline now...
+	if ($suppress_line_start_match)
+	{
+	    $suppress_line_start_match = 0;
+	}
+	elsif (($this_line =~ /^(\s*)\*\s+[a-zA-Z0-9]/)
+	       || ($this_line =~ /^(\s*)\* [a-zA-Z0-9_\.\/\+-]+/)
+	       || ($this_line =~ /^(\s*)\([a-zA-Z0-9_\.\/\+-]+(\)|,\s*)/)
+	       || ($this_line =~ /^(\s+)(\S+)/)
+	       || ($this_line =~ /^(\s*)- +/)
+	       || ($this_line =~ /^()\s*$/)
+	       || ($this_line =~ /^(\s*)\*\) +/)
+	       || ($this_line =~ /^(\s*)[a-zA-Z0-9](\)|\.|\:) +/))
+	{
+	    $length_remaining = $max_line_length - (length ($user_indent));
+	}
+
+	# Now that any user_indent has been preserved, strip off leading
+	# whitespace, so up-folding has no ugly side-effects.
+	$this_line =~ s/^\s*//;
+
+	# Accumulate the line, and adjust parameters for next line.
+	my $this_len = length ($this_line);
+	if ($this_len == 0)
+	{
+	    # Blank lines should cancel any user_indent level.
+	    $user_indent = '';
+	    $length_remaining = $max_line_length;
+	}
+	elsif ($this_len >= $length_remaining) # Line too long, try breaking it.
+	{
+	    # Walk backwards from the end.  At first acceptable spot, break
+	    # a new line.
+	    my $idx = $length_remaining - 1;
+	    if ($idx < 0) { $idx = 0 };
+	    while ($idx > 0)
+	    {
+		if (substr ($this_line, $idx, 1) =~ /\s/)
+		{
+		    my $line_now = substr ($this_line, 0, $idx);
+		    my $next_line = substr ($this_line, $idx);
+		    $this_line = $line_now;
+
+		    # Clean whitespace off the end.
+		    chomp $this_line;
+
+		    # The current line is ready to be printed.
+		    $this_line .= "\n${left_pad_str}";
+
+		    # Make sure the next line is allowed full room.
+		    $length_remaining = $max_line_length - (length ($user_indent));
+
+		    # Strip next_line, but then preserve any user_indent.
+		    $next_line =~ s/^\s*//;
+
+		    # Sneak a peek at the user_indent of the upcoming line, so
+		    # $next_line (which will now precede it) can inherit that
+		    # indent level.  Otherwise, use whatever user_indent level
+		    # we currently have, which might be none.
+		    my $next_next_line = shift (@lines);
+		    if ((defined ($next_next_line)) && ($next_next_line =~ /^(\s+)/)) {
+			$next_line = $1 . $next_line if (defined ($1));
+			# $length_remaining = $max_line_length - (length ($1));
+			$next_next_line =~ s/^\s*//;
+		    }
+		    else {
+			$next_line = $user_indent . $next_line;
+		    }
+		    if (defined ($next_next_line)) {
+			unshift (@lines, $next_next_line);
+		    }
+		    unshift (@lines, $next_line);
+
+		    # Our new next line might, coincidentally, begin with one of
+		    # the line-start regexps, so we temporarily turn off
+		    # sensitivity to that until we're past the line.
+		    $suppress_line_start_match = 1;
+
+		    last;
+		}
+		else
+		{
+		    $idx--;
+		}
+	    }
+
+	    if ($idx == 0)
+	    {
+		# We bottomed out because the line is longer than the
+		# available space.  But that could be because the space is
+		# small, or because the line is longer than even the maximum
+		# possible space.  Handle both cases below.
+
+		if ($length_remaining == ($max_line_length - (length ($user_indent))))
+		{
+		    # The line is simply too long -- there is no hope of ever
+		    # breaking it nicely, so just insert it verbatim, with
+		    # appropriate padding.
+		    $this_line = "\n${left_pad_str}${this_line}";
+		}
+		else
+		{
+		    # Can't break it here, but may be able to on the next round...
+		    unshift (@lines, $this_line);
+		    $length_remaining = $max_line_length - (length ($user_indent));
+		    $this_line = "\n${left_pad_str}";
+		}
+	    }
+	}
+	else  # $this_len < $length_remaining, so tack on what we can.
+	{
+	    # Leave a note for the next iteration.
+	    $length_remaining = $length_remaining - $this_len;
+
+	    if ($this_line =~ /\.$/)
+	    {
+		$this_line .= "  ";
+		$length_remaining -= 2;
+	    }
+	    else  # not a sentence end
+	    {
+		$this_line .= " ";
+		$length_remaining -= 1;
+	    }
+	}
+
+	# Unconditionally indicate that loop has run at least once.
+	$first_time = 0;
+
+	$wrapped_text .= "${user_indent}${this_line}";
+    }
+
+    # One last bit of padding.
+    $wrapped_text .= "\n";
+
+    return $wrapped_text;
+}
+
+# main
+
+my @date;
+my $author;
+my @files;
+my $comment;
+my $merge;
+
+my $state; # 0-header 1-comment 2-files
+my $done = 0;
+
+$state = 0;
+
+while (<>) {
+    #print STDERR "debug ($state, " . (@date ? (strftime "%Y-%m-%d", @date) : "") . "): `$_'\n";
+
+    if ($state == 0) {
+	if (m,^Author: (.*),) {
+	    $author = $1;
+	}
+	if (m,^Date: (.*),) {
+	    @date = strptime($1);
+	}
+	if (m,^Merge: (.*),) {
+	    $merge = 1;
+	}
+	$state = 1 if (m,^$,);
+    } elsif ($state == 1) {
+	$state = 2 if (m,^$,);
+	s/^    //g;
+	s/\n/ /g;
+	$comment = $comment . $_;
+    } elsif ($state == 2 && $merge) {
+	$done = 1;
+    } elsif ($state == 2) {
+	if (m,^([-0-9]+)\t([-0-9]+)\t(.*)$,) {
+	    push @files, $3;
+	} elsif (m,^[^ ],) {
+	    # No file changes.
+	    $done = 1;
+	}
+	$done = 1 if (m,^$,);
+    }
+
+    if ($done && @date == ()) {
+	print STDERR "warning: could not parse entry\n";
+    } elsif ($done) {
+	print (strftime "%Y-%m-%d  $author\n\n", @date);
+
+	my $files = join (", ", @files);
+	$files = mywrap ("\t", "\t", "* $files"), ": ";
+
+	if (index($comment, EMPTY_LOG_MESSAGE) > -1 ) {
+	    $comment = "[no log message]\n";
+	}
+
+	my $files_last_line_len = 0;
+	$files_last_line_len = last_line_len($files) + 1;
+	my $msg = wrap_log_entry($comment, "\t", 69-$files_last_line_len, 69);
+
+	$msg =~ s/[ \t]+\n/\n/g;
+
+	if ($merge) {
+	    print "\t$msg\n";
+	} else {
+	    print "$files: $msg\n";
+	}
+
+	@date = ();
+	$author = "";
+	@files = ();
+	$comment = "";
+	$merge = 0;
+
+	$state = 0;
+	$done = 0;
+    }
+}
+
+if (@files) {
+    print (strftime "%Y-%m-%d  $author\n\n", @date);
+    my $msg = wrap_log_entry($comment, "\t", 69, 69);
+    $msg =~ s/[ \t]+\n/\n/g;
+    print "\t* $msg\n";
+}