#!/usr/pkg/bin/python2.7
# vim: set ts=4 sw=4 et:

#
# Copyright (C) 2009 Novell, Inc.
#
# Authors: Vincent Untz <vuntz@gnome.org>
#
# 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., 675 Mass Ave, Cambridge, MA 02139, USA.
#

import optparse
import os
import shutil
import sys
import urllib
import ConfigParser

try:
    import bonobo
    has_bonobo = True
except ImportError:
    has_bonobo = False

import gconf

PANEL_SCHEMAS_OBJECTS_DIR='/schemas/apps/panel/objects'

PANEL_TOPLEVELS_DIR='/apps/panel/toplevels'
PANEL_TOPLEVELS_KEY='/apps/panel/general/toplevel_id_list'
PANEL_APPLETS_DIR='/apps/panel/applets'
PANEL_APPLETS_KEY='/apps/panel/general/applet_id_list'
PANEL_OBJECTS_DIR='/apps/panel/objects'
PANEL_OBJECTS_KEY='/apps/panel/general/object_id_list'

PANEL_LAUNCHER_USER_DIR=os.path.join(os.path.expanduser('~'), '.gnome2', 'panel2.d', 'default', 'launchers')

PANEL_TYPE_NONE = 0
PANEL_TYPE_APPLET = 1
PANEL_TYPE_LAUNCHER = 2

PANEL_APPLETS_EXTENSION='.panel-applet'
PANEL_APPLETS_PATH='/usr/pkg/share/gnome-panel/applets'
PANEL_APPLET_FACTORY_GROUP='Applet Factory'

class PanelAddException(Exception):
    pass

class PanelAdder:

    def __init__(self, toplevel, position, right_stick, applet_iid = None, launcher_path = None, copy_launcher = False):
        self.toplevel = toplevel
        self.position = position
        self.right_stick = right_stick

        # FIXME workaround: -1 and right_click don't play well together. Just
        # use an arbitrary value so that the applet ends up at the left of all
        # right-click applets (it'd be surprising to have a config with more
        # than 10 applets...)
        if self.right_stick and self.position < 0:
            self.position = 10

        self.type = PANEL_TYPE_NONE
        self.applet_iid = None
        self.launcher_path = None
        self.copy_launcher = False

        self.client = None

        if applet_iid:
            self._set_applet_iid(applet_iid)
            self.type = PANEL_TYPE_APPLET
        elif launcher_path:
            self._set_launcher_path(launcher_path)
            self.copy_launcher = copy_launcher
            self.type = PANEL_TYPE_LAUNCHER

    def _panel_get_subdirs(self, key):
        return [ os.path.basename(s) for s in self.client.all_dirs(key) ]

    def _panel_get_list(self, key):
        list_v = self.client.get(key)

        if not list_v:
            return []

        if list_v.type != gconf.VALUE_LIST or list_v.get_list_type() != gconf.VALUE_STRING:
            return []

        return [ v.get_string() for v in list_v.get_list() ]

    def _toplevel_id_ensure(self):
        toplevel_subdirs = self._panel_get_subdirs(PANEL_TOPLEVELS_DIR)
        toplevel_id_list = self._panel_get_list(PANEL_TOPLEVELS_KEY)

        if self.toplevel:
            if not self.toplevel in toplevel_subdirs or not self.toplevel in toplevel_id_list:
                raise PanelAddException('%s is not an existing panel identifier' % self.toplevel)
        else:
            for toplevel in toplevel_id_list:
                if toplevel in toplevel_subdirs:
                    self.toplevel = toplevel
                    return

            raise PanelAddException('Cannot find a panel identifier')

    def _get_applet_list(self):
        dirs = os.getenv('GNOME_PANEL_APPLETS_DIR')
        if not dirs:
            dirs = PANEL_APPLETS_PATH

        retval = []
        dups = []
        for path in dirs.split(":"):
            if not os.path.isdir(path):
                continue

            if path in dups:
                continue

            dups.append(path)

            for f in os.listdir(path):
                if not f.endswith(PANEL_APPLETS_EXTENSION):
                    continue

                config = ConfigParser.RawConfigParser()
                try:
                    config.read(os.path.join(path, f))
                except ConfigParser.ParsingError:
                    continue

                fid = config.get(PANEL_APPLET_FACTORY_GROUP, 'Id')
                retval.extend(["%s::%s" % (fid, s) for s in config.sections() if s != PANEL_APPLET_FACTORY_GROUP])

        return retval

    def _set_applet_iid(self, applet_iid):
        if not applet_iid:
            raise PanelAddException('No applet specified')

        applets = self._get_applet_list ()
        if not applet_iid in applets:
            if has_bonobo:
                applets = bonobo.activation.query ("has_all (repo_ids, ['IDL:Bonobo/Control:1.0', 'IDL:GNOME/Vertigo/PanelAppletShell:1.0'])")
                if not applet_iid in [ a.iid for a in applets ]:
                    raise PanelAddException('%s is not a valid applet' % applet_iid)
            else:
                raise PanelAddException('%s is not a valid applet' % applet_iid)

        self.applet_iid = applet_iid

    def _set_launcher_path(self, launcher_path):
        if not launcher_path:
            raise PanelAddException('No launcher specified')

        if not launcher_path.endswith('.desktop'):
            raise PanelAddException('%s does not have a .desktop extension' % launcher_path)

        if not os.path.exists(launcher_path):
            raise PanelAddException('%s is not an existing launcher' % launcher_path)

        self.launcher_path = launcher_path

    def _object_id_find_unused(self):
        if self.type == PANEL_TYPE_APPLET:
            subdirs = self._panel_get_subdirs(PANEL_APPLETS_DIR)
            id_list = self._panel_get_list(PANEL_APPLETS_KEY)
            prefix = 'applet'
        else:
            subdirs = self._panel_get_subdirs(PANEL_OBJECTS_DIR)
            id_list = self._panel_get_list(PANEL_OBJECTS_KEY)
            prefix = 'object'

        i = 0
        while i < 1000:
            id = '%s_%d' % (prefix, i)
            if id not in subdirs and id not in id_list:
                return id
            i += 1

        return None

    def _launcher_find_unused(self):
        basename = os.path.basename(self.launcher_path)
        if not os.path.exists(os.path.join(PANEL_LAUNCHER_USER_DIR, basename)):
            return basename

        dot = basename.rfind('.')
        prefix = basename[:dot]
        extension = basename[dot:]

        i = 0
        while i < 1000:
            basename = '%s-%d%s' % (prefix, i, extension)
            if not os.path.exists(os.path.join(PANEL_LAUNCHER_USER_DIR, basename)):
                return basename
            i += 1

        raise PanelAddException('Copying %s would overwrite existing launchers' % self.launcher_path)

    def _associate_schemas(self, engine, schema_dir, dest_dir):
        entries = self.client.all_entries(schema_dir)
        for entry in entries:
            dest_key = os.path.basename(entry.key)
            engine.associate_schema(os.path.join(dest_dir, dest_key), entry.key)

        subdirs = self.client.all_dirs(schema_dir)
        for subdir in subdirs:
            dest_subdir = os.path.join(subdir)
            self._associate_schemas(engine, subdir, os.path.join(dest_dir, dest_subdir))

    def run(self):
        self.client = gconf.client_get_default ()
        self._toplevel_id_ensure()

        id = self._object_id_find_unused()
        if not id:
            raise PanelAddException('No identifier available for the new object')

        if self.type == PANEL_TYPE_APPLET:
            dir = os.path.join(PANEL_APPLETS_DIR, id)

        elif self.type == PANEL_TYPE_LAUNCHER:
            dir = os.path.join(PANEL_OBJECTS_DIR, id)

            if self.copy_launcher:
                try:
                    if not os.path.exists(PANEL_LAUNCHER_USER_DIR):
                        os.makedirs(PANEL_LAUNCHER_USER_DIR)
                    unused = self._launcher_find_unused()
                    dest = os.path.join (PANEL_LAUNCHER_USER_DIR, unused)
                    shutil.copyfile(self.launcher_path, dest)

                    launcher_location = unused
                except (OSError, IOError), e:
                    raise PanelAddException('Cannot copy launcher: %s' % e)
            else:
                launcher_location = 'file://' + urllib.pathname2url(os.path.realpath(self.launcher_path))

        else:
            raise PanelAddException('Unknown panel object type %d' % self.type)

        engine = gconf.engine_get_default()
        self._associate_schemas(engine, PANEL_SCHEMAS_OBJECTS_DIR, dir)

        self.client.set_string(os.path.join(dir, 'toplevel_id'), self.toplevel)
        self.client.set_int(os.path.join(dir, 'position'), self.position)
        self.client.set_bool(os.path.join(dir, 'panel_right_stick'), self.right_stick)

        if self.type == PANEL_TYPE_APPLET:
            self.client.set_string(os.path.join(dir, 'object_type'), 'bonobo-applet')
            self.client.set_string(os.path.join(dir, 'applet_iid'), self.applet_iid)
            id_list = self._panel_get_list(PANEL_APPLETS_KEY)
            id_list.append(id)
            self.client.set_list(PANEL_APPLETS_KEY, gconf.VALUE_STRING, id_list)

        elif self.type == PANEL_TYPE_LAUNCHER:
            self.client.set_string(os.path.join(dir, 'object_type'), 'launcher-object')
            self.client.set_string(os.path.join(dir, 'launcher_location'), launcher_location)
            id_list = self._panel_get_list(PANEL_OBJECTS_KEY)
            id_list.append(id)
            self.client.set_list(PANEL_OBJECTS_KEY, gconf.VALUE_STRING, id_list)

        else:
            raise PanelAddException('Unknown panel object type %d' % self.type)

def main(args):
    parser = optparse.OptionParser()

    parser.add_option('--applet', dest='applet',
                      help='Applet to add')
    parser.add_option('--copy-launcher', dest='copy_launcher',
                      action='store_true', default=False,
                      help='Copy the launcher to the user directory')
    parser.add_option('--launcher', dest='launcher',
                      help='Launcher to add')
    parser.add_option('--panel', dest='toplevel',
                      help='Identifier of the panel where to add the applet')
    parser.add_option('--position', dest='position',
                      help='Position on the panel where to add the applet')
    parser.add_option('--right-stick', dest='right_stick',
                      action='store_true', default=False,
                      help='Make the applet right-aligned on the panel')

    (options, args) = parser.parse_args()

    if not options.applet and not options.launcher:
        print parser.format_help()
        return 1

    if options.applet and options.launcher:
        raise PanelAddException('Cannot add an applet and a launcher at the same time')

    if options.position:
        try:
            position = int(options.position)
        except ValueError:
            raise PanelAddException('%s is not an integer, required for position' % options.position)
    else:
        position = -1

    adder = PanelAdder(options.toplevel, position, options.right_stick, options.applet, options.launcher, options.copy_launcher)
    adder.run()

if __name__ == '__main__':
    try:
      main(sys.argv)
    except PanelAddException, e:
        print >> sys.stderr, e
        sys.exit(1)
    except KeyboardInterrupt:
      pass
