#!/usr/bin/env python # -*- coding: utf-8 -*- # Mitter, a simple client for Twitter # Copyright (C) 2007, 2008 The Mitter Contributors # # 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 3 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, see . import logging import os.path import urllib2 import gettext from mitterlib.network.networkbase import NetworkData from mitterlib import module_search _log = logging.getLogger('mitterlib.network.Networks') # List of files that are not networks SKIPPABLES = ('__init__.py', 'networkbase.py') #-------------------------------------------------------------------- # i18n #-------------------------------------------------------------------- t = gettext.translation('networks', fallback=True) _ = t.gettext N_ = t.ngettext #-------------------------------------------------------------------- # Helper functions #-------------------------------------------------------------------- def _import_name(module): """Based on the name of the module, return the proper "import" statement.""" (name, _) = os.path.splitext(module) return 'mitterlib.network.%s' % (name) #-------------------------------------------------------------------- # Exceptions #-------------------------------------------------------------------- class NetworksError(Exception): """Basic Networks exception.""" pass class NetworksNoSuchNetworkError(NetworksError): """The request network does not exists.""" def __init__(self, network): self._network = network def __str__(self): return _('Unknown network %s') % (self._network) pass class NetworksNoNetworkSetupError(NetworksError): """There are no networks set up.""" def __str__(self): return _('No networks set up.') pass class NetworksErrorLoadingNetwork(NetworksError): """Error loadig one of the networks.""" def __init__(self, network, exception): self._network = network self._exception = str(exception) def __str__(self): return "Couldn't load %s (%s)" % (self._network, self._exception) class Networks(object): """Network transparency layer: Keeps a list of available networks and send requests to all those who have been setup.""" def __init__(self, options): self._networks = {} self._options = options self._operations = 0 self._proxy_handler = None self.options() @property def networks(self): if self._networks: return self._networks for module_name in module_search(__file__, SKIPPABLES): import_name = _import_name(module_name) module = __import__(import_name, fromlist=[import_name]) connection = module.Connection(self._options) self._networks[connection.SHORTCUT] = connection return self._networks def user(self, network=None): """Return a :class:`NetworkUser` representation for the logged user in the requested network. If no network is selected, will return the first found.""" shortcut = self._targets(network).next() # need this due yield return self.networks[shortcut].user def _targets(self, shortcut=None): """Select a network based on the shortcut. If the shortcut is None, returns all available network shortcuts.""" if shortcut: if not shortcut in self.networks: raise NetworksNoSuchNetworkError(shortcut) else: targets = [shortcut] else: targets = self.networks setup = False for target in targets: if self.networks[target].is_setup: setup = True yield target if not setup: raise NetworksNoNetworkSetupError return def _save(self): """Check the number of operations and, if it above the minimum value, save the config file.""" max_count = self._options['NetworkManager']['save_count'] _log.debug('Operations %d, max %d', self._operations, max_count) if self._operations > max_count: self._options.save() self._operations = 0 _log.debug('Config file saved') return def _proxy(self): """Check and install the proxy.""" if not self._options['NetworkManager']['proxy']: return if self._proxy_handler: return proxy = self._options['NetworkManager']['proxy'] if not '://' in proxy: proxy = 'http://' + proxy _log.debug('Installing proxy ' + proxy) self._proxy_handler = urllib2.ProxyHandler({ 'http': proxy, 'https': proxy}) opener = urllib2.build_opener(self._proxy_handler) urllib2.install_opener(opener) return def settings(self): """Return a dictionary with the options that the interfaces need to setup.""" result = [] for shortcut in self.networks: settings = { 'shortcut': shortcut, 'name': self.networks[shortcut].NAMESPACE, 'options': self.networks[shortcut].AUTH, } result.append(settings) return result def options(self): """Add the options for all networks and the network manager options itself.""" # Remember that, in case you add or remove any options here, you # should also update the CHEAT-CODES file. for shortcut in self.networks: conn = self.networks[shortcut] conn.options(self._options) # add Networks own options self._options.add_group('NetworkManager', 'Network Manager') self._options.add_option( '--timeout', group='NetworkManager', option='timeout', help='Timeout for requests in all networks.', type='int', metavar='SECONDS', default=120) self._options.add_option( '--proxy', group='NetworkManager', option='proxy', help='Proxy server (Note: This is not needed on Windows " \ "and OS X systems as Mitter will use the system " \ "value.)', metavar='SERVER', default='') self._options.add_option( group='NetworkManager', option='save_count', help='Number of network operations till the config file ' \ 'is saved', type='int', metavar='OPERATIONS', default=5, is_cmd_option=False) return def name(self, shortcut): """Return the name of a network based on the shortcut.""" try: name = self.networks[shortcut].NAMESPACE except KeyError: raise NetworksNoSuchNetworkError(shortcut) return name # This is basically a copy of all methods available in NetworkBase, with # the additional parameter "network" (to request data from just one # source) def messages(self, network=None): """Return a list of NetworkData objects for the main "timeline" (the default list presented to the user.)""" self._proxy() result = [] for shortcut in self._targets(network): for message in self.networks[shortcut].messages(): message.network = shortcut message.network_name = self.networks[shortcut].NAMESPACE result.append(message) self._operations += 1 # don't check the operations count inside the loop; if Mitter # fails, all the last messages can still be retrieved ('cause the # config file wasn't saved yet.) self._save() return result def replies(self, network=None): """Return a list of NetworkData objects for the replies for the user messages.""" result = [] self._proxy() for shortcut in self._targets(network): for message in self.networks[shortcut].replies(): message.network = shortcut message.network_name = self.networks[shortcut].NAMESPACE result.append(message) self._operations += 1 self._save() return result def update(self, status, reply_to=None, network=None): """Update the user status. Must return the id for the new status.""" self._proxy() if reply_to and isinstance(reply_to, NetworkData): # If you pass a NetworkData object, we get the proper network network = reply_to.network results = [] for shortcut in self._targets(network): results.append(self.networks[shortcut].update(status, reply_to)) self._operations += 1 self._save() return results def repost(self, message): """Repost a message in the user's timeline. The network used is the same in the message.""" assert(isinstance(message, NetworkData)) self._proxy() self.networks[message.network].repost(message) self._operations += 1 self._save() return def favorite(self, message): """Change the favorite status of the message. Returns the new favorite status.""" assert(isinstance(message, NetworkData)) self._proxy() self.networks[message.network].favorite(message) self._operations += 1 self._save() # since the operation completed without a problem, we can toggle the # message as favorited already. message.favorite = not message.favorite return message.favorite def delete_message(self, message, network=None): """Delete an update. Message can be a :class:`NetworkData` object, in which case network is not necessary; otherwise a network must be provided. Always return the same message object""" if isinstance(message, NetworkData): network = message.network self._proxy() self.networks[network].delete_message(message) self._operations += 1 self._save() return message def message(self, message_id, network): """Return a single NetworkData object for a specified message.""" # TODO: This should probably be named parent() and receive a # NetworkData. Still returns a single NetworkData object. if not network in self.networks: raise NetworksNoSuchNetworkError(network) self._proxy() data = self.networks[network].message(message_id) data.network = network self._operations += 1 self._save() return data def available_requests(self, network=None): """Return a dictionary with the available requests the user can make to each network before getting capped.""" result = {} for shortcut in self._targets(network): requests = self.networks[shortcut].available_requests() result[shortcut] = requests return result