Changeset - 8996a3694def
[Not reviewed]
0 4 1
Lance Edgar (lance) - 12 years ago 2012-09-18 16:25:53
lance@edbob.org
add dbsync command, linux daemon
5 files changed with 197 insertions and 146 deletions:
0 comments (0 inline, 0 general)
rattail/commands.py
Show inline comments
 
@@ -53,6 +53,33 @@ See the file COPYING.txt for more information.
 
"""
 

	
 

	
 
class DatabaseSyncCommand(commands.Subcommand):
 
    """
 
    Interacts with the database synchronization service; called as ``rattail
 
    dbsync``.
 
    """
 

	
 
    name = 'dbsync'
 
    description = "Manage the database synchronization service"
 

	
 
    def add_parser_args(self, parser):
 
        subparsers = parser.add_subparsers(title='subcommands')
 

	
 
        start = subparsers.add_parser('start', help="Start service")
 
        start.set_defaults(subcommand='start')
 
        stop = subparsers.add_parser('stop', help="Stop service")
 
        stop.set_defaults(subcommand='stop')
 

	
 
    def run(self, args):
 
        from rattail.db.sync import linux as dbsync
 

	
 
        if args.subcommand == 'start':
 
            dbsync.start_daemon()
 

	
 
        elif args.subcommand == 'stop':
 
            dbsync.stop_daemon()
 

	
 

	
 
class FileMonitorCommand(commands.FileMonitorCommand):
 
    """
 
    Interacts with the file monitor service; called as ``rattail filemon``.
 
@@ -71,60 +98,6 @@ class FileMonitorCommand(commands.FileMonitorCommand):
 
        return RattailFileMonitor
 

	
 

	
 
class InitCommand(commands.Subcommand):
 
    """
 
    Initializes the database; called as ``{{package}} initialize``.  This is
 
    meant to be leveraged as part of setting up the application.  The database
 
    used by this command will be determined by config, for example::
 

	
 
    .. highlight:: ini
 

	
 
       [edbob.db]
 
       sqlalchemy.url = postgresql://user:pass@localhost/{{package}}
 
    """
 

	
 
    name = 'initialize'
 
    description = "Initialize the database"
 

	
 
    def run(self, args):
 
        from edbob.db import engine
 
        from edbob.db.util import install_core_schema
 
        from edbob.db.exceptions import CoreSchemaAlreadyInstalled
 
        from edbob.db.extensions import activate_extension
 

	
 
        # Install core schema to database.
 
        try:
 
            install_core_schema(engine)
 
        except CoreSchemaAlreadyInstalled, err:
 
            print '%s:' % err
 
            print '  %s' % engine.url
 
            return
 

	
 
        # Activate any extensions you like here...
 
        # activate_extension('shrubbery')
 

	
 
        # Okay, on to bootstrapping...
 

	
 
        from edbob.db import Session
 
        from edbob.db.classes import Role, User
 
        from edbob.db.auth import administrator_role
 

	
 
        session = Session()
 

	
 
        # Create 'admin' user with full rights.
 
        admin = User(username='admin', password='admin')
 
        admin.roles.append(administrator_role(session))
 
        session.add(admin)
 

	
 
        # Do any other bootstrapping you like here...
 
        
 
        session.commit()
 
        session.close()
 
        
 
        print "Initialized database:"
 
        print '  %s' % engine.url
 

	
 

	
 
class LoadHostDataCommand(commands.Subcommand):
 
    """
 
    Loads data from the Rattail host database, if one is configured.
rattail/db/sync/__init__.py
Show inline comments
 
@@ -26,3 +26,114 @@
 
``rattail.db.sync`` -- Database Synchronization
 
"""
 

	
 
import sys
 
import time
 
import logging
 

	
 
if sys.platform == 'win32':
 
    import win32api
 

	
 
from sqlalchemy.orm import class_mapper
 

	
 
import edbob
 

	
 
import rattail
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
def get_sync_engines():
 
    edbob.init_modules(['edbob.db'])
 

	
 
    keys = edbob.config.get('rattail.db', 'syncs')
 
    if not keys:
 
        return None
 

	
 
    engines = {}
 
    for key in keys.split(','):
 
        key = key.strip()
 
        engines[key] = edbob.engines[key]
 
    log.info("get_sync_engines: Found engine keys: %s" % ','.join(engines.keys()))
 
    return engines
 

	
 

	
 
def dependency_sort(x, y):
 
    map_x = class_mapper(getattr(edbob, x))
 
    map_y = class_mapper(getattr(edbob, y))
 

	
 
    dep_x = []
 
    table_y = map_y.tables[0].name
 
    for column in map_x.columns:
 
        for key in column.foreign_keys:
 
            if key.column.table.name == table_y:
 
                return 1
 
            dep_x.append(key)
 

	
 
    dep_y = []
 
    table_x = map_x.tables[0].name
 
    for column in map_y.columns:
 
        for key in column.foreign_keys:
 
            if key.column.table.name == table_x:
 
                return -1
 
            dep_y.append(key)
 

	
 
    if dep_x and not dep_y:
 
        return 1
 
    if dep_y and not dep_x:
 
        return -1
 
    return 0
 

	
 

	
 
def synchronize_changes(engines):
 

	
 
    log.info("synchronize_changes: Using engine keys: %s" % ','.join(engines.keys()))
 

	
 
    while True:
 
        local_session = edbob.Session()
 
        local_changes = local_session.query(rattail.Change)
 

	
 
        if local_changes.count():
 
            log.debug('synchronize_changes: found %d changes' % local_changes.count())
 

	
 
            class_names = []
 
            for class_name in local_session.query(rattail.Change.class_name.distinct()):
 
                class_names.append(class_name[0])
 
            class_names.sort(cmp=dependency_sort)
 

	
 
            remote_sessions = []
 
            for remote_engine in engines.itervalues():
 
                remote_sessions.append(
 
                    edbob.Session(bind=remote_engine))
 

	
 
            for class_name in class_names:
 

	
 
                for change in local_changes.filter_by(class_name=class_name):
 
                    cls = getattr(edbob, change.class_name)
 

	
 
                    if change.deleted:
 
                        for remote_session in remote_sessions:
 
                            remote_instance = remote_session.query(cls).get(change.uuid)
 
                            if remote_instance:
 
                                remote_session.delete(remote_instance)
 
                                remote_session.flush()
 

	
 
                    else: # new/dirty
 
                        local_instance = local_session.query(cls).get(change.uuid)
 
                        for remote_session in remote_sessions:
 
                            remote_session.merge(local_instance)
 
                            remote_session.flush()
 

	
 
                    local_session.delete(change)
 
                    local_session.flush()
 

	
 
            for remote_session in remote_sessions:
 
                remote_session.commit()
 
                remote_session.close()
 
            local_session.commit()
 

	
 
        local_session.close()
 
        if sys.platform == 'win32':
 
            win32api.Sleep(3000)
 
        else:
 
            time.sleep(3)
rattail/db/sync/linux.py
Show inline comments
 
new file 100644
 
#!/usr/bin/env python
 
# -*- coding: utf-8  -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2012 Lance Edgar
 
#
 
#  This file is part of Rattail.
 
#
 
#  Rattail is free software: you can redistribute it and/or modify it under the
 
#  terms of the GNU Affero General Public License as published by the Free
 
#  Software Foundation, either version 3 of the License, or (at your option)
 
#  any later version.
 
#
 
#  Rattail 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 Affero General Public License for
 
#  more details.
 
#
 
#  You should have received a copy of the GNU Affero General Public License
 
#  along with Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 

	
 
"""
 
``rattail.db.sync.linux`` -- Database Synchronization for Linux
 
"""
 

	
 
from edbob.daemon import Daemon
 

	
 
from rattail.db.sync import get_sync_engines, synchronize_changes
 

	
 

	
 
class SyncDaemon(Daemon):
 

	
 
    def run(self):
 
        engines = get_sync_engines()
 
        if engines:
 
            synchronize_changes(engines)
 

	
 

	
 
def get_daemon():
 
    return SyncDaemon('/tmp/rattail_dbsync.pid')
 

	
 

	
 
def start_daemon():
 
    get_daemon().start()
 

	
 

	
 
def stop_daemon():
 
    get_daemon().stop()
rattail/db/sync/win32.py
Show inline comments
 
@@ -30,17 +30,10 @@ import sys
 
import logging
 
import threading
 

	
 
if sys.platform == 'win32': # docs should build for everyone
 
    import win32api
 
    import win32serviceutil
 

	
 
from sqlalchemy import engine_from_config
 
from sqlalchemy.orm import class_mapper
 

	
 
import edbob
 
from edbob.win32 import Service
 

	
 
import rattail
 
from rattail.db.sync import get_sync_engines, synchronize_changes
 

	
 

	
 
log = logging.getLogger(__name__)
 
@@ -67,16 +60,11 @@ class DatabaseSynchronizerService(Service):
 
        if not Service.Initialize(self):
 
            return False
 

	
 
        edbob.init_modules(['edbob.db', 'rattail.db'])
 

	
 
        keys = edbob.config.get('rattail.db', 'syncs')
 
        if not keys:
 
        engines = get_sync_engines()
 
        if not engines:
 
            return False
 

	
 
        engines = {}
 
        for key in keys.split(','):
 
            key = key.strip()
 
            engines[key] = edbob.engines[key]
 
        edbob.init_modules(['rattail.db'])
 

	
 
        thread = threading.Thread(target=synchronize_changes,
 
                                  args=(engines,))
 
@@ -86,80 +74,7 @@ class DatabaseSynchronizerService(Service):
 
        return True
 

	
 

	
 
def dependency_sort(x, y):
 
    map_x = class_mapper(getattr(edbob, x))
 
    map_y = class_mapper(getattr(edbob, y))
 

	
 
    dep_x = []
 
    table_y = map_y.tables[0].name
 
    for column in map_x.columns:
 
        for key in column.foreign_keys:
 
            if key.column.table.name == table_y:
 
                return 1
 
            dep_x.append(key)
 

	
 
    dep_y = []
 
    table_x = map_x.tables[0].name
 
    for column in map_y.columns:
 
        for key in column.foreign_keys:
 
            if key.column.table.name == table_x:
 
                return -1
 
            dep_y.append(key)
 

	
 
    if dep_x and not dep_y:
 
        return 1
 
    if dep_y and not dep_x:
 
        return -1
 
    return 0
 

	
 

	
 
def synchronize_changes(engines):
 

	
 
    while True:
 
        local_session = edbob.Session()
 
        local_changes = local_session.query(rattail.Change)
 

	
 
        if local_changes.count():
 

	
 
            class_names = []
 
            for class_name in local_session.query(rattail.Change.class_name.distinct()):
 
                class_names.append(class_name[0])
 
            class_names.sort(cmp=dependency_sort)
 

	
 
            remote_sessions = []
 
            for remote_engine in engines.itervalues():
 
                remote_sessions.append(
 
                    edbob.Session(bind=remote_engine))
 

	
 
            for class_name in class_names:
 

	
 
                for change in local_changes.filter_by(class_name=class_name):
 
                    cls = getattr(edbob, change.class_name)
 

	
 
                    if change.deleted:
 
                        for remote_session in remote_sessions:
 
                            remote_instance = remote_session.query(cls).get(change.uuid)
 
                            if remote_instance:
 
                                remote_session.delete(remote_instance)
 
                                remote_session.flush()
 

	
 
                    else: # new/dirty
 
                        local_instance = local_session.query(cls).get(change.uuid)
 
                        for remote_session in remote_sessions:
 
                            remote_session.merge(local_instance)
 
                            remote_session.flush()
 

	
 
                    local_session.delete(change)
 
                    local_session.flush()
 

	
 
            for remote_session in remote_sessions:
 
                remote_session.commit()
 
                remote_session.close()
 
            local_session.commit()
 

	
 
        local_session.close()
 
        win32api.Sleep(3000)
 

	
 
    
 
if __name__ == '__main__':
 
    win32serviceutil.HandleCommandLine(DatabaseSynchronizerService)
 
    if sys.platform == 'win32':
 
        import win32serviceutil
 
        win32serviceutil.HandleCommandLine(DatabaseSynchronizerService)
setup.py
Show inline comments
 
@@ -112,6 +112,7 @@ rattailw = rattail.commands:main
 
rattail = rattail.db.extension:RattailExtension
 

	
 
[rattail.commands]
 
dbsync = rattail.commands:DatabaseSyncCommand
 
filemon = rattail.commands:FileMonitorCommand
 
load-host-data = rattail.commands:LoadHostDataCommand
 

	
0 comments (0 inline, 0 general)