1# -*- coding: utf-8 -*-
2
3# Copyright(C) 2010-2012 Romain Bignon
4#
5# This file is part of weboob.
6#
7# weboob is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Lesser General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# weboob is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public License
18# along with weboob. If not, see <http://www.gnu.org/licenses/>.
19
20from __future__ import print_function
21
22from copy import copy
23
24from weboob.core import CallErrors
25from weboob.tools.application.repl import ReplApplication
26from weboob.applications.boobmsg import Boobmsg
27from weboob.capabilities.dating import CapDating, OptimizationNotFound
28from weboob.tools.application.formatters.iformatter import PrettyFormatter
29
30
31__all__ = ['HaveDate']
32
33
34class EventListFormatter(PrettyFormatter):
35    MANDATORY_FIELDS = ('date', 'type')
36
37    def get_title(self, event):
38        s = u'(%s) %s' % (event.date, event.type)
39        if hasattr(event, 'contact') and event.contact:
40            s += u' — %s (%s)' % (event.contact.name, event.contact.id)
41
42        return s
43
44    def get_description(self, event):
45        if hasattr(event, 'message'):
46            return event.message
47
48
49class HaveDate(Boobmsg):
50    APPNAME = 'havedate'
51    VERSION = '2.0'
52    COPYRIGHT = 'Copyright(C) 2010-YEAR Romain Bignon'
53    DESCRIPTION = "Console application allowing to interact with various dating websites " \
54                  "and to optimize seduction algorithmically."
55    SHORT_DESCRIPTION = "interact with dating websites"
56    STORAGE_FILENAME = 'dating.storage'
57    STORAGE = {'optims': {}}
58    CAPS = CapDating
59    EXTRA_FORMATTERS = copy(Boobmsg.EXTRA_FORMATTERS)
60    EXTRA_FORMATTERS['events'] = EventListFormatter
61    COMMANDS_FORMATTERS = copy(Boobmsg.COMMANDS_FORMATTERS)
62    COMMANDS_FORMATTERS['optim'] = 'table'
63    COMMANDS_FORMATTERS['events'] = 'events'
64
65    def load_default_backends(self):
66        self.load_backends(CapDating, storage=self.create_storage(self.STORAGE_FILENAME))
67
68    def main(self, argv):
69        self.load_config()
70
71        try:
72            self.do('init_optimizations').wait()
73        except CallErrors as e:
74            self.bcall_errors_handler(e)
75
76        optimizations = self.storage.get('optims')
77        for optim, backends in optimizations.items():
78            self.optims('start', backends, optim, store=False)
79
80        return ReplApplication.main(self, argv)
81
82    def do_query(self, id):
83        """
84        query ID
85
86        Send a query to someone.
87        """
88        _id, backend_name = self.parse_id(id, unique_backend=True)
89
90        for query in self.do('send_query', _id, backends=backend_name):
91            print('%s' % query.message)
92
93    def edit_optims(self, backend_names, optims_names, stop=False):
94        if optims_names is None:
95            print('Error: missing parameters.', file=self.stderr)
96            return 2
97
98        for optim_name in optims_names.split():
99            backends_optims = {}
100            for optim in self.do('get_optimization', optim_name, backends=backend_names):
101                if optim:
102                    backends_optims[optim.backend] = optim
103            for backend_name, optim in backends_optims.items():
104                if len(optim.CONFIG) == 0:
105                    print('%s.%s does not require configuration.' % (backend_name, optim_name))
106                    continue
107
108                was_running = optim.is_running()
109                if stop and was_running:
110                    print('Stopping %s: %s' % (optim_name, backend_name))
111                    optim.stop()
112                params = optim.get_config()
113                if params is None:
114                    params = {}
115                print('Configuration of %s.%s' % (backend_name, optim_name))
116                print('-----------------%s-%s' % ('-' * len(backend_name), '-' * len(optim_name)))
117                for key, value in optim.CONFIG.items():
118                    params[key] = self.ask(value, default=params[key] if (key in params) else value.default)
119
120                optim.set_config(params)
121                if stop and was_running:
122                    print('Starting %s: %s' % (optim_name, backend_name))
123                    optim.start()
124
125    def optims(self, function, backend_names, optims, store=True):
126        if optims is None:
127            print('Error: missing parameters.', file=self.stderr)
128            return 2
129
130        for optim_name in optims.split():
131            try:
132                if store:
133                    storage_optim = set(self.storage.get('optims', optim_name, default=[]))
134                self.stdout.write('%sing %s:' % (function.capitalize(), optim_name))
135                for optim in self.do('get_optimization', optim_name, backends=backend_names):
136                    if optim:
137                        # It's useless to start a started optim, or to stop a stopped one.
138                        if (function == 'start' and optim.is_running()) or \
139                           (function == 'stop' and not optim.is_running()):
140                            continue
141
142                        # Optim is not configured and would be, ask user to do it.
143                        if function == 'start' and len(optim.CONFIG) > 0 and optim.get_config() is None:
144                            self.edit_optims(optim.backend, optim_name)
145
146                        ret = getattr(optim, function)()
147                        self.stdout.write(' ' + optim.backend)
148                        if not ret:
149                            self.stdout.write('(failed)')
150                        self.stdout.flush()
151                        if store:
152                            if function == 'start' and ret:
153                                storage_optim.add(optim.backend)
154                            elif function == 'stop':
155                                try:
156                                    storage_optim.remove(optim.backend)
157                                except KeyError:
158                                    pass
159                self.stdout.write('.\n')
160            except CallErrors as errors:
161                for backend, error, backtrace in errors:
162                    if isinstance(error, OptimizationNotFound):
163                        self.logger.error(u'Error(%s): Optimization "%s" not found' % (backend.name, optim_name))
164                    else:
165                        self.bcall_error_handler(backend, error, backtrace)
166            if store:
167                if len(storage_optim) > 0:
168                    self.storage.set('optims', optim_name, list(storage_optim))
169                else:
170                    self.storage.delete('optims', optim_name)
171        if store:
172            self.storage.save()
173
174    def complete_optim(self, text, line, *ignored):
175        args = line.split(' ')
176        if len(args) == 2:
177            return ['list', 'start', 'stop', 'edit']
178        elif len(args) == 3:
179            return [backend.name for backend in self.enabled_backends]
180        elif len(args) >= 4:
181            if args[2] == '*':
182                backend = None
183            else:
184                backend = args[2]
185            optims = set()
186            for optim in self.do('iter_optimizations', backends=backend):
187                optims.add(optim.id)
188            return sorted(optims - set(args[3:]))
189
190    def do_optim(self, line):
191        """
192        optim [list | start | edit | stop] BACKEND [OPTIM1 [OPTIM2 ...]]
193
194        All dating backends offer optimization services. This command can be
195        manage them.
196        Use * us BACKEND value to apply command to all backends.
197
198        Commands:
199        * list       list all available optimizations of a backend
200        * start      start optimization services on a backend
201        * edit       configure an optimization service for a backend
202        * stop       stop optimization services on a backend
203        """
204        cmd, backend_name, optims_names = self.parse_command_args(line, 3)
205
206        if backend_name == '*':
207            backend_name = None
208        elif backend_name is not None and backend_name not in [b.name for b in self.enabled_backends]:
209            print('Error: No such backend "%s"' % backend_name, file=self.stderr)
210            return 1
211
212        if cmd == 'start':
213            return self.optims('start', backend_name, optims_names)
214        if cmd == 'stop':
215            return self.optims('stop', backend_name, optims_names)
216        if cmd == 'edit':
217            self.edit_optims(backend_name, optims_names, stop=True)
218            return
219        if cmd == 'list' or cmd is None:
220            if optims_names is not None:
221                optims_names = optims_names.split()
222
223            optims = {}
224            backends = set()
225            for optim in self.do('iter_optimizations', backends=backend_name):
226                if optims_names is not None and optim.id not in optims_names:
227                    continue
228                if optim.is_running():
229                    status = 'RUNNING'
230                else:
231                    status = '-------'
232                if optim.id not in optims:
233                    optims[optim.id] = {optim.backend: status}
234                else:
235                    optims[optim.id][optim.backend] = status
236                backends.add(optim.backend)
237
238            backends = sorted(backends)
239            for name, backends_status in optims.items():
240                line = [('name', name)]
241                for b in backends:
242                    try:
243                        status = backends_status[b]
244                    except KeyError:
245                        status = ''
246                    line.append((b, status))
247                self.format(tuple(line))
248            return
249        print("No such command '%s'" % cmd, file=self.stderr)
250        return 1
251
252    def do_events(self, line):
253        """
254        events
255
256        Display dating events.
257        """
258        self.change_path([u'events'])
259        self.start_format()
260        for event in self.do('iter_events'):
261            self.cached_format(event)
262