""" Config.py

Module for Straw's configuration settings.
"""
__copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc."
__license__ = """ GNU General Public License

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., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA. """

import cPickle, os, traceback
from urlparse import urlparse
from error import log, logtb, logparam
import pygtk
pygtk.require('2.0')
import gconf
import Event
import LookupManager

GCONF_STRAW_ROOT = "/apps/straw"

OPTION_LAST_POLL = "/general/last_poll"
OPTION_ITEMS_STORED = "/general/number_of_items_stored"
OPTION_ITEM_ORDER = "/general/item_order_newest"
OPTION_BROWSER_CMD = "/general/browser_cmd"
OPTION_WINDOW_SIZE_W = "/ui/window_width"
OPTION_WINDOW_SIZE_H = "/ui/window_height"
OPTION_MAIN_PANE_POS = "/ui/main_pane_position"
OPTION_SUB_PANE_POS = "/ui/sub_pane_position"
OPTION_WINDOW_MAX = "/ui/window_maximized"
OPTION_MAGNIFICATION = "/ui/text_magnification"
OPTION_OFFLINE = "/general/offline"
OPTION_POLL_FREQUENCY = "/general/poll_frequency"
OPTION_FEED_ID_SEQ = "feed_id_seq"
OPTION_FEEDS = "feeds"
OPTION_CATEGORIES = "categories"

def ensure_directory(strawdir):
    if os.path.exists(strawdir):
        if not os.path.isdir(strawdir):
            raise Exception
        return 0
    os.mkdir(strawdir)
    return 1

#############
# Config persistence classes
class ConfigPicklePersistence:
    def __init__(self, filename):
        self._config_file = filename
        self._dict = None

    def save_option(self, option, value):
        if self._dict is None:
            self._initialize_dict()
        temp_config_file = self._config_file + ".working"
        pickle_file = open(temp_config_file, "w")
        self._dict[option] = value
        cPickle.dump(self._dict, pickle_file, True)
        pickle_file.close()
        os.rename(temp_config_file, self._config_file)
        return

    def load_option(self, option):
        if self._dict is None:
            self._initialize_dict()
        return self._dict.get(option, None)

    def _initialize_dict(self):
        if os.path.exists(self._config_file):
            pickle_file = open(self._config_file, "r")
            self._dict = cPickle.load(pickle_file)
            pickle_file.close()
        else:
            self._dict = {}

class ConfigGConfPersistence:
    SAVERS = {OPTION_LAST_POLL: 'int',
              OPTION_ITEMS_STORED: 'int',
              OPTION_ITEM_ORDER: 'bool',
              OPTION_BROWSER_CMD: 'string',
              OPTION_WINDOW_SIZE_W: 'int',
              OPTION_WINDOW_SIZE_H: 'int',
              OPTION_MAIN_PANE_POS: 'int',
              OPTION_SUB_PANE_POS: 'int',
              OPTION_OFFLINE: 'bool',
              OPTION_WINDOW_MAX: 'bool',
              OPTION_MAGNIFICATION: 'float',
              OPTION_POLL_FREQUENCY: 'int'}

    def __init__(self, client):
        self.client = client

    def save_option(self, option, value):
        getattr(self.client, 'set_' + self.SAVERS[option])(
            GCONF_STRAW_ROOT + option, value)

    def load_option(self, option):
        return getattr(self.client, 'get_' + self.SAVERS[option])(
            GCONF_STRAW_ROOT + option)

class ConfigPersistence:
    def __init__(self, *backends):
        self.backends = backends

    def save_option(self, option, value):
        for b in self.backends:
            if option in b[1]:
                b[0].save_option(option, value)

    def load_option(self, option):
        for b in self.backends:
            if option in b[1]:
                return b[0].load_option(option)

################
# Proxy config
class BaseProxyConfig(object):
    def __init__(self):
        self._config_working = False

    @property
    def config_working(self):
        return self._config_working

    @apply
    def host():
        doc = ""
        def fget(self):
            return self._host
        def fset(self, host):
            self._host = host
            self._ip = None
        return property(**locals())

    @apply
    def ip():
        doc = ""
        def fget(self):
            return self._ip
        def fset(self, ip):
            assert self._ip is None, "Overwriting proxy IP not allowed!"
            if ip is None:
                self.use = 0
            self._ip = ip
        return property(**locals())

    @property
    def is_waiting(self):
        return self.use and not self._ip

    def _ip_lookup_callback(self, host, ip, data):
        self.ip = ip

    def lookup_host(self):
        if self._host and not self._ip:
            LookupManager.get_instance().lookup(
                self._host, self._ip_lookup_callback)


class GconfProxyConfig(BaseProxyConfig):
    """Encapsulate proxy use and location information (host, port, ip),
    gconf reading and name lookup logic"""

    GCONF_PROXY_ROOT = "/system/proxy"
    GCONF_PROXY_MODE = GCONF_PROXY_ROOT + "/mode"
    GCONF_HTTP_PROXY_ROOT = "/system/http_proxy"
    GCONF_HTTP_PROXY_HOST = GCONF_HTTP_PROXY_ROOT + "/host"
    GCONF_HTTP_PROXY_PORT = GCONF_HTTP_PROXY_ROOT + "/port"
    GCONF_HTTP_PROXY_USE_AUTHENTICATION = GCONF_HTTP_PROXY_ROOT + "/use_authentication"
    GCONF_HTTP_PROXY_USER = GCONF_HTTP_PROXY_ROOT + "/authentication_user"
    GCONF_HTTP_PROXY_PASSWORD = GCONF_HTTP_PROXY_ROOT + "/authentication_password"

    def __init__(self):
        BaseProxyConfig.__init__(self)
        client = gconf.client_get_default()
        self._config_working = client.dir_exists(self.GCONF_PROXY_ROOT) and client.dir_exists(self.GCONF_HTTP_PROXY_ROOT)
        if not self._config_working:
            return
        client.add_dir(self.GCONF_PROXY_ROOT, gconf.CLIENT_PRELOAD_RECURSIVE)
        client.add_dir(self.GCONF_HTTP_PROXY_ROOT, gconf.CLIENT_PRELOAD_RECURSIVE)
        client.notify_add(self.GCONF_PROXY_MODE, self.proxy_mode_changed)
        client.notify_add(self.GCONF_HTTP_PROXY_HOST, self.proxy_host_changed)
        client.notify_add(self.GCONF_HTTP_PROXY_PORT, self.proxy_port_changed)
        client.notify_add(self.GCONF_HTTP_PROXY_USE_AUTHENTICATION,
                          self.proxy_auth_changed)
        client.notify_add(self.GCONF_HTTP_PROXY_USER, self.proxy_auth_changed)
        client.notify_add(self.GCONF_HTTP_PROXY_PASSWORD, self.proxy_auth_changed)

        self._ip = None
        pm = client.get_string(self.GCONF_PROXY_MODE)
        self.use = pm is not None and pm != "none"
        self.host = client.get_string(self.GCONF_HTTP_PROXY_HOST)
        self.port = client.get_int(self.GCONF_HTTP_PROXY_PORT)
        self.use_authentication = client.get_bool(
            self.GCONF_HTTP_PROXY_USE_AUTHENTICATION)
        self.user = client.get_string(self.GCONF_HTTP_PROXY_USER)
        self.password = client.get_string(self.GCONF_HTTP_PROXY_PASSWORD)

    # here be gconf logic
    def proxy_mode_changed(self, client, notify_id, entry, *args):
        value = entry.value.get_string()
        self.use = value is not None and value != "none"

    def proxy_host_changed(self, client, notify_id, entry, *args):
        value = entry.value.get_string()
        if value is not None and len(value):
            self.host = value
        else:
            self.use = 0

    def proxy_port_changed(self, client, notify_id, entry, *args):
        value = entry.value.get_int()
        if value is not None and value > 0:
            self.port = value
        else:
            self.use = 0

    def proxy_auth_changed(self, client, notify_id, entry, *args):
        value = entry.value
        if entry.key == self.GCONF_HTTP_PROXY_USE_AUTHENTICATION:
            if not value:
                self.use_authentication = 0
            else:
                self.use_authentication = 1
        elif entry.key == self.GCONF_HTTP_PROXY_USER:
            self.user = value
        elif entry.key == self.GCONF_HTTP_PROXY_PASSWORD:
            self.password = value

class EnvironmentProxyConfig(BaseProxyConfig):
    """Encapsulate proxy use and location information, environment reading"""

    def __init__(self):
        BaseProxyConfig.__init__(self)
        try:
            result = self._read_env()
        except:
            result = 0
        self._config_working = result
        self.use = self._config_working

    def _read_env(self):
        proxy = os.getenv("http_proxy")
        if not proxy:
            return 0
        pparts = urlparse(proxy)
        proto = pparts[0]
        host = pparts[1]
        if proto != "http" or not len(host):
            return 0
        hparts = host.split('@')
        auth = self.user = self.password = self.use_authentication = None
        if len(hparts) > 2:
            return 0
        if len(hparts) == 2:
            auth, host = hparts
        if auth:
            aparts = auth.split(':')
            if len(aparts) != 2:
                return 0
            self.user, self.password = aparts
            self.use_authentication = 1
        self.host = host
        self.port = 80
        hparts = host.split(':')
        if len(hparts) > 2:
            return 0
        if len(hparts) == 2:
            self.host = hparts[0]
            self.port = int(hparts[1])
        return 1

class NullProxyConfig(BaseProxyConfig):
    def __init__(self):
        BaseProxyConfig.__init__(self)
        self.use = 0
        self._config_working = 0
        self._host = None
        self._ip = None
        self.port = None
        self.user = None
        self.password = None
        self.use_authentication = None

###################
# The main man
class Config(object, Event.SignalEmitter):
    _straw_dir = os.path.join(os.getenv("HOME"), ".straw")
    _straw_config_file = os.path.join(_straw_dir, "config")

    def __init__(self, persistence):
        Event.SignalEmitter.__init__(self)
        self.persistence = persistence
        self.initialize_slots(Event.ItemOrderChangedSignal,
                              Event.OfflineModeChangedSignal,
                              Event.PollFrequencyChangedSignal,
                              Event.NumberOfItemsStoredChangedSignal)
        self._feed_id_seq = 0
        self._poll_freq = 1800
        self._last_poll = 0
        self._browser_cmd = ''
        self._items_stored = 30
        self._item_order = False
        self._main_window_size = (640,480)
        self._main_pane_position = 100
        self._sub_pane_position = 100
        self.first_time = None
        self._offline = True
        self._window_maximized = False
        self._use_threads = True
        self._reload_css = False
        self._no_etags = False

    def initialize_proxy(self):
        pcchoices = (GconfProxyConfig, EnvironmentProxyConfig, NullProxyConfig)
        for p in pcchoices:
            self._proxy_config = p()
            if self._proxy_config.config_working:
                break

    def initialize_options(self):
        # Call this after calling Config's constructor
        self.first_time = ensure_directory(self._straw_dir)

        if os.path.exists(self.straw_config_file):
            self._feed_id_seq = self.persistence.load_option(OPTION_FEED_ID_SEQ)

        self._poll_freq = self.persistence.load_option(OPTION_POLL_FREQUENCY)
        self._last_poll = self.persistence.load_option(OPTION_LAST_POLL)
        self._items_stored = self.persistence.load_option(OPTION_ITEMS_STORED)
        self._browser_cmd = self.persistence.load_option(OPTION_BROWSER_CMD)
        self._item_order = self.persistence.load_option(OPTION_ITEM_ORDER)

        width = self.persistence.load_option(OPTION_WINDOW_SIZE_W)
        height = self.persistence.load_option(OPTION_WINDOW_SIZE_H)
        if width <= 0:
            width = 640
        if height <= 0:
            height = 480
        self._main_window_size = (width, height)

        self._main_pane_position = self.persistence.load_option(
            OPTION_MAIN_PANE_POS)
        self._sub_pane_position = self.persistence.load_option(
            OPTION_SUB_PANE_POS)
        self._window_maximized = self.persistence.load_option(OPTION_WINDOW_MAX)
        self._text_magnification = self.persistence.load_option(OPTION_MAGNIFICATION)
        self._offline = self.persistence.load_option(OPTION_OFFLINE)

    @property
    def straw_dir(self):
        return self._straw_dir

    @property
    def straw_config_file(self):
        return self._straw_config_file

    @property
    def proxy_config(self):
        return self._proxy_config

    @apply
    def feeds():
        doc = "Marshalled feed data"
        def fget(self):
            return self.persistence.load_option(OPTION_FEEDS)
        def fset(self, feeddata):
            self.persistence.save_option(
                OPTION_FEEDS, feeddata)
        return property(**locals())

    @apply
    def categories():
        doc = ""
        def fget(self):
            return self.persistence.load_option(OPTION_CATEGORIES)
        def fset(self, categorydata):
            self.persistence.save_option(
                OPTION_CATEGORIES, categorydata)
        return property(**locals())
    
    @apply
    def poll_frequency():
        doc = "Polling frequency"
        def fget(self):
            return self._poll_freq
        def fset(self, poll_frequency):
            if self._poll_freq != poll_frequency:
                self._poll_freq = poll_frequency
                self.persistence.save_option(OPTION_POLL_FREQUENCY, poll_frequency)
                self.emit_signal(Event.PollFrequencyChangedSignal(self, poll_frequency))
        return property(**locals())

    @apply
    def last_poll():
        doc = "last poll"
        def fget(self):
            return self._last_poll
        def fset(self, last_poll):
            if self._last_poll != last_poll:
                self._last_poll = last_poll
                self.persistence.save_option(OPTION_LAST_POLL, last_poll)
        return property(**locals())

    @apply
    def  browser_cmd():
        doc = "The browser to use"
        def fget(self):
            return self._browser_cmd
        def fset(self, browser_cmd):
            if self._browser_cmd != browser_cmd:
                self._browser_cmd = browser_cmd
                self.persistence.save_option(OPTION_BROWSER_CMD, browser_cmd)
        return property(**locals())

    @apply
    def number_of_items_stored():
        doc = "Number of items to store per feed"
        def fget(self):
            return self._items_stored
        def fset(self, num=30):
            if self._items_stored != num:
                self._items_stored = num
                self.persistence.save_option(OPTION_ITEMS_STORED, num)
                self.emit_signal(Event.NumberOfItemsStoredChangedSignal(self))
        return property(**locals())

    @apply
    def item_order():
        doc = "Ordering of Items"
        def fget(self):
            return self._item_order
        def fset(self, order):
            if self._item_order != order:
                self._item_order = order
                self.persistence.save_option(OPTION_ITEM_ORDER, order)
                self.emit_signal(Event.ItemOrderChangedSignal(self))
        return property(**locals())

    @apply
    def feed_id_seq():
        doc = ""
        def fget(self):
            return self._feed_id_seq
        def fset(self, id):
            self._feed_id_seq = id
            self.persistence.save_option(OPTION_FEED_ID_SEQ, id)
        return property(**locals())

    def next_feed_id_seq(self):
        self.feed_id_seq += 1
        return self._feed_id_seq

    @apply
    def main_window_size():
        doc = ""
        def fget(self):
            return self._main_window_size
        def fset(self, size):
            if self._main_window_size != size:
                self._main_window_size = size
                self.persistence.save_option(OPTION_WINDOW_SIZE_W, size[0])
                self.persistence.save_option(OPTION_WINDOW_SIZE_H, size[1])
        return property(**locals())

    @apply
    def offline():
        doc = ""
        def fget(self):
            return self._offline
        def fset(self, mode):
            if self._offline != mode:
                self._offline = mode
                self.persistence.save_option(OPTION_OFFLINE, mode)
                self.emit_signal(Event.OfflineModeChangedSignal(self))
        return property(**locals())

    @apply
    def window_maximized():
        doc = ""
        def fget(self):
            return self._window_maximized
        def fset(self, state):
            if self._window_maximized is not state:
                self._window_maximized = state
                self.persistence.save_option(OPTION_WINDOW_MAX, state)
        return property(**locals())

    @apply
    def main_pane_position():
        doc = ""
        def fget(self):
            return self._main_pane_position
        def fset(self, position):
            if self._main_pane_position != position:
                self._main_pane_position = position
                self.persistence.save_option(OPTION_MAIN_PANE_POS, position)
        return property(**locals())

    @apply
    def sub_pane_position():
        doc = ""
        def fget(self):
            return self._sub_pane_position
        def fset(self, position):
            if self._sub_pane_position != position:
                self._sub_pane_position = position
                self.persistence.save_option(OPTION_SUB_PANE_POS, position)
        return property(**locals())

    @apply
    def text_magnification():
        doc = "sets the amount of magnification in the item view"
        def fget(self):
            return self._text_magnification
        def fset(self, tm):
            if self._text_magnification is not tm:
                self._text_magnification = tm
                self.persistence.save_option(OPTION_MAGNIFICATION, tm)
        return property(**locals())

    @apply
    def use_threads():
        doc = ""
        def fget(self):
            return self._use_threads
        def fset(self, threads):
            self._use_threads = threads
        return property(**locals())

    @apply
    def reload_css():
        doc = ""
        def fget(self):
            return self._reload_css
        def fset(self, reload_css):
            self._reload_css = reload_css
        return property(**locals())

    @apply
    def no_etags():
        doc = ""
        def fget(self):
            return self._no_etags
        def fset(self, no_etags):
            self._no_etags = no_etags
        return property(**locals())

def convert_if_necessary(config):
    if not os.path.exists(config.straw_config_file):
        return

    try:
        f = open(config.straw_config_file, "r")
        cf = cPickle.load(f)

        feeds = cf.get('feeds', None)
        if feeds and feeds[0].get('_n_items_unread', None) is None:

            def _convert_feeds(feeds, parent):
                import ItemStore
                itemstore = ItemStore.get_instance(config.straw_dir)
                for feed in feeds:
                    if isinstance(feed, list):
                        _convert_feeds(feed[1:], parent)
                    else:
                        stored = feed.get('_items_stored', -1)
                        if stored > -1:
                            cutoff = stored
                        else:
                            cutoff = config.number_of_items_stored
                        feed['_n_items_unread'] = itemstore.get_number_of_unread(feed['_id'], cutoff)
                return feeds

            config.feeds = _convert_feeds(feeds, None)

        if cf.has_key('poll_frequency'):
            config.poll_frequency = cf.get('poll_frequency')
            config.number_of_items_stored = cf.get('number_of_items_stored')
            config.item_order = cf.get('item_order')
            config.main_window_size = cf.get('main_window_size')
            config.main_pane_position = cf.get('main_pane_position')
            config.sub_pane_position = cf.get('sub_pane_position')
            config.offline = cf.get('offline')

            del cf['poll_frequency']
            del cf['number_of_items_stored']
            del cf['item_order']
            del cf['main_window_size']
            del cf['main_pane_position']
            del cf['sub_pane_position']
            del cf['offline']

            f.close()
            f = open(config.straw_config_file, "w")
            f.seek(0)
            f.truncate()
            cPickle.dump(cf, f, True)
    finally:
        f.close()
    return

def create_gconf_persistence():
    client = gconf.client_get_default()
    client.add_dir(GCONF_STRAW_ROOT, gconf.CLIENT_PRELOAD_ONELEVEL)
    return ConfigGConfPersistence(client)

def create_pickle_persistence():
    return ConfigPicklePersistence(Config._straw_config_file)

def create_instance():
    # Add your GConf key here as well!
    cp = ConfigPersistence(
        (create_gconf_persistence(),
         (OPTION_LAST_POLL, OPTION_ITEMS_STORED, OPTION_ITEM_ORDER, OPTION_BROWSER_CMD,
          OPTION_WINDOW_SIZE_W, OPTION_WINDOW_SIZE_H,
          OPTION_MAIN_PANE_POS, OPTION_SUB_PANE_POS, OPTION_OFFLINE,
          OPTION_MAGNIFICATION,
          OPTION_POLL_FREQUENCY, OPTION_WINDOW_MAX)),
        (create_pickle_persistence(),
         (OPTION_FEED_ID_SEQ, OPTION_FEEDS, OPTION_CATEGORIES)))
    config = Config(cp)
    config.initialize_proxy()
    config.initialize_options()
    convert_if_necessary(config)
    return config

config_instance = None
def get_instance():
    global config_instance
    if config_instance is None:
        config_instance = create_instance()
    return config_instance


syntax highlighted by Code2HTML, v. 0.9.1