~ubuntu-branches/ubuntu/utopic/gozerbot/utopic

« back to all changes in this revision

Viewing changes to build/lib/gozerbot/fleet.py

  • Committer: Package Import Robot
  • Author(s): Jeremy Malcolm
  • Date: 2012-04-03 21:58:28 UTC
  • mfrom: (3.1.11 sid)
  • Revision ID: package-import@ubuntu.com-20120403215828-6mik0tzug5na93la
Tags: 0.99.1-2
* Removes logfiles on purge (Closes: #668767)
* Reverted location of installed files back to /usr/lib/gozerbot.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# gozerbot/fleet.py
 
2
#
 
3
#
 
4
 
 
5
""" fleet is a list of bots. """
 
6
 
 
7
__copyright__ = 'this file is in the public domain'
 
8
 
 
9
# ==============
 
10
# IMPORT SECTION
 
11
 
 
12
# gozerbot imports
 
13
 
 
14
from gozerbot.datadir import datadir
 
15
from utils.exception import handle_exception
 
16
from utils.generic import waitforqueue
 
17
from utils.log import rlog
 
18
from utils.locking import lockdec
 
19
from threads.thr import start_new_thread, threaded
 
20
from config import Config, fleetbotconfigtxt, config
 
21
from users import users
 
22
from plugins import plugins
 
23
from simplejson import load
 
24
 
 
25
# basic imports
 
26
 
 
27
import Queue, os, types, threading, time, pickle, glob, logging, shutil, thread
 
28
 
 
29
# END IMPORT
 
30
 
 
31
# ============
 
32
# LOCK SECTION
 
33
 
 
34
fleetlock = thread.allocate_lock()
 
35
fleetlocked = lockdec(fleetlock)
 
36
 
 
37
# END LOCK
 
38
# ========
 
39
 
 
40
## START
 
41
 
 
42
class FleetBotAlreadyExists(Exception):
 
43
    pass
 
44
 
 
45
 
 
46
class Fleet(object):
 
47
 
 
48
    """
 
49
        a fleet contains multiple bots (list of bots). used the datadir
 
50
        set in gozerbot/datadir.py
 
51
 
 
52
    """
 
53
 
 
54
    def __init__(self):
 
55
        self.datadir = datadir + os.sep + 'fleet'
 
56
        if hasattr(os, 'mkdir'):
 
57
            if not os.path.exists(self.datadir):
 
58
                os.mkdir(self.datadir)
 
59
        self.startok = threading.Event()
 
60
        self.bots = []
 
61
 
 
62
    def getfirstbot(self):
 
63
 
 
64
        """
 
65
            return the main bot of the fleet.
 
66
 
 
67
            :rtype: gozerbot.botbase.BotBase
 
68
 
 
69
            .. literalinclude:: ../../gozerbot/fleet.py
 
70
                :pyobject: Fleet.getfirstbot
 
71
 
 
72
        """
 
73
        self.startok.wait()
 
74
        return self.bots[0]
 
75
 
 
76
    def getfirstjabber(self):
 
77
 
 
78
        """
 
79
            return the first jabber bot of the fleet.
 
80
 
 
81
            :rtype: gozerbot.botbase.BotBase
 
82
 
 
83
            .. literalinclude:: ../../gozerbot/fleet.py
 
84
                :pyobject: Fleet.getfirstjabber
 
85
 
 
86
        """
 
87
 
 
88
        self.startok.wait()
 
89
 
 
90
        for bot in self.bots:
 
91
            if bot.type == 'xmpp':
 
92
               return bot
 
93
        
 
94
    def size(self):
 
95
 
 
96
        """
 
97
             return number of bots in fleet.
 
98
 
 
99
             :rtype: integer
 
100
 
 
101
             .. literalinclude:: ../../gozerbot/fleet.py
 
102
                 :pyobject: Fleet.size
 
103
 
 
104
        """
 
105
 
 
106
        return len(self.bots)
 
107
 
 
108
    def resume(self, sessionfile):
 
109
 
 
110
        """
 
111
            resume bot from session file.
 
112
 
 
113
            :param sessionfile: filename of the session data file
 
114
            :type sessionfile: string
 
115
 
 
116
            .. literalinclude:: ../../gozerbot/fleet.py
 
117
                :pyobject: Fleet.resume
 
118
        """
 
119
 
 
120
        # read JSON session file
 
121
        session = load(open(sessionfile))
 
122
 
 
123
        #  resume bots in session file
 
124
        for name in session['bots'].keys():
 
125
            reto = None
 
126
            if name == session['name']:
 
127
                reto = session['channel']
 
128
            start_new_thread(self.resumebot, (name, session['bots'][name], reto))
 
129
 
 
130
        # allow 5 seconds for bots to resurrect
 
131
        time.sleep(5)
 
132
 
 
133
        # set start event
 
134
        self.startok.set()
 
135
 
 
136
    def makebot(self, name, cfg=None):
 
137
 
 
138
        """
 
139
            create a bot .. use configuration if provided.
 
140
 
 
141
            :param name: the name of the bot
 
142
            :type name: string
 
143
            :param cfg: configuration file for the bot
 
144
            :type cfg: gozerbot.config.Config
 
145
 
 
146
            .. literalinclude:: ../../gozerbot/fleet.py
 
147
                :pyobject: Fleet.makebot
 
148
 
 
149
        """
 
150
 
 
151
        if self.byname(name):
 
152
            raise FleetBotAlreadyExists("there is already a %s bot in the fleet" % name)
 
153
 
 
154
        rlog(10, 'fleet', 'making bot')
 
155
        bot = None
 
156
 
 
157
        # if not config create a default bot
 
158
        if not cfg:
 
159
            cfg = Config(self.datadir + os.sep + name, 'config', inittxt=fleetbotconfigtxt)
 
160
            cfg.save()
 
161
 
 
162
        # create bot based on type 
 
163
        if cfg['type'] == 'irc':
 
164
            from gozerbot.irc.bot import Bot
 
165
            bot = Bot(name, cfg)
 
166
        elif cfg['type'] == 'xmpp' or cfg['type'] == 'jabber':
 
167
            from gozerbot.xmpp.bot import Bot
 
168
            bot = Bot(name, cfg)
 
169
        elif cfg['type'] == 'gozernet':
 
170
            from gozerbot.gozernet.bot import GozerNetBot
 
171
            bot = GozerNetBot(name, cfg)
 
172
        else:
 
173
            rlog(10, 'fleet', '%s .. unproper type: %s' % (cfg['name'], cfg['type']))
 
174
 
 
175
        # set bot name and initialize bot
 
176
        if bot:
 
177
            cfg['name'] = bot.name = name
 
178
            self.initbot(bot)
 
179
            return bot
 
180
 
 
181
        # failed to created the bot
 
182
        raise Exception("can't make %s bot" % name)
 
183
 
 
184
    def resumebot(self, botname, data={}, printto=None):
 
185
 
 
186
        """
 
187
            resume individual bot.
 
188
 
 
189
            :param botname: name of the bot to resume
 
190
            :type botname: string
 
191
            :param data: resume data
 
192
            :type data: dict
 
193
            :param printto: whom to reply to that resuming is done
 
194
            :type printto: nick or JID
 
195
 
 
196
            .. literalinclude:: ../../gozerbot/fleet.py
 
197
                :pyobject: Fleet.resumebot
 
198
 
 
199
        """
 
200
 
 
201
        # see if we need to exit the old bot
 
202
        oldbot = self.byname(botname)
 
203
        if oldbot:
 
204
            oldbot.exit()
 
205
 
 
206
        # recreate config file of the bot
 
207
        cfg = Config(datadir + os.sep + 'fleet' + os.sep + botname, 'config')
 
208
 
 
209
        # make the bot and resume (IRC) or reconnect (Jabber)
 
210
        bot = self.makebot(botname, cfg)
 
211
        rlog(100, 'fleet', 'bot made: %s' % str(bot))
 
212
 
 
213
        if bot:
 
214
            if oldbot:
 
215
                self.replace(oldbot, bot)
 
216
            else:
 
217
                self.bots.append(bot)
 
218
 
 
219
            if not bot.jabber:
 
220
                bot._resume(data, printto)
 
221
            else:
 
222
                start_new_thread(bot.connectwithjoin, ())
 
223
 
 
224
    def start(self, botlist=[], enable=False):
 
225
 
 
226
        """
 
227
            startup the bots.
 
228
 
 
229
            :param botlist: list of bots to start .. if not provided the bots in the gozerdata/fleet dir will be used
 
230
            :type botlist: list
 
231
            :param enable: whether the bot should be enabled 
 
232
            :type enable: boolean
 
233
 
 
234
            .. literalinclude:: ../../gozerbot/fleet.py
 
235
                :pyobject: Fleet.start
 
236
 
 
237
        """
 
238
 
 
239
        # scan the fleet datadir for bots
 
240
        dirs = []
 
241
        got = []
 
242
        for bot in botlist:
 
243
            dirs.append(self.datadir + os.sep + bot)
 
244
 
 
245
        if not dirs:
 
246
            dirs = glob.glob(self.datadir + os.sep + "*")
 
247
 
 
248
        for fleetdir in dirs:
 
249
 
 
250
            if fleetdir.endswith('fleet'):
 
251
                continue
 
252
 
 
253
            rlog(10, 'fleet', 'found bot: ' + fleetdir)
 
254
            cfg = Config(fleetdir, 'config')
 
255
 
 
256
            if not cfg:
 
257
                rlog(10, 'fleet', "can't read %s config file" % fleetdir)
 
258
                continue
 
259
 
 
260
            name = fleetdir.split(os.sep)[-1]
 
261
 
 
262
            if not name:
 
263
                rlog(10, 'fleet', "can't read botname from %s config file" % \
 
264
fleetdir)
 
265
                continue
 
266
 
 
267
            if not enable and not cfg['enable']:
 
268
                rlog(10, 'fleet', '%s bot is disabled' % name)
 
269
                continue
 
270
            else:
 
271
                rlog(10, 'fleet', '%s bot is enabled' % name)
 
272
 
 
273
            if not name in fleetdir:
 
274
                rlog(10, 'fleet', 'bot name in config file doesnt match dir name')
 
275
                continue
 
276
 
 
277
            try:
 
278
                bot = self.makebot(name, cfg)
 
279
            except FleetBotAlreadyExists:
 
280
                rlog(10, 'fleet', 'there is already a fleetbot with the name %s' % name)
 
281
                continue
 
282
 
 
283
            if bot:
 
284
                self.addbot(bot)
 
285
                start_new_thread(bot.connectwithjoin, ())
 
286
                got.append(bot)
 
287
 
 
288
        # set startok event
 
289
        self.startok.set()
 
290
 
 
291
        return got
 
292
 
 
293
    def save(self):
 
294
 
 
295
        """
 
296
            save fleet data and call save on all the bots.
 
297
 
 
298
            .. literalinclude:: ../../gozerbot/fleet.py
 
299
                :pyobject: Fleet.save
 
300
 
 
301
        """
 
302
 
 
303
        for i in self.bots:
 
304
 
 
305
            try:
 
306
                i.save()
 
307
            except Exception, ex:
 
308
                handle_exception()
 
309
 
 
310
    def avail(self):
 
311
 
 
312
        """
 
313
            show available fleet bots.
 
314
 
 
315
            :rtype: list
 
316
 
 
317
            .. literalinclude:: ../../gozerbot/fleet.py
 
318
                :pyobject: Fleet.avail
 
319
 
 
320
        """
 
321
 
 
322
        return os.listdir(self.datadir)
 
323
 
 
324
    def list(self):
 
325
 
 
326
        """
 
327
            return list of bot names.
 
328
 
 
329
            :rtype: list
 
330
 
 
331
            .. literalinclude:: ../../gozerbot/fleet.py
 
332
                :pyobject: Fleet.list
 
333
 
 
334
        """
 
335
 
 
336
        result = []
 
337
 
 
338
        for i in self.bots:
 
339
            result.append(i.name)
 
340
 
 
341
        return result
 
342
 
 
343
    def stopall(self):
 
344
 
 
345
        """ 
 
346
            call stop() on all fleet bots.
 
347
 
 
348
            .. literalinclude:: ../../gozerbot/fleet.py
 
349
                :pyobject: Fleet.stopall
 
350
 
 
351
        """
 
352
 
 
353
        for i in self.bots:
 
354
 
 
355
            try:
 
356
                i.stop()
 
357
            except:
 
358
                pass
 
359
 
 
360
    def byname(self, name):
 
361
 
 
362
        """
 
363
            return bot by name.
 
364
 
 
365
            :param name: name of the bot
 
366
            :type name: string
 
367
 
 
368
            .. literalinclude:: ../../gozerbot/fleet.py
 
369
                :pyobject: Fleet.byname
 
370
 
 
371
        """
 
372
 
 
373
        for i in self.bots:
 
374
            if name == i.name:
 
375
                return i
 
376
 
 
377
    def replace(self, name, bot):
 
378
 
 
379
        """
 
380
            replace bot with a new bot.
 
381
 
 
382
            :param name: name of the bot to replace
 
383
            :type name: string
 
384
            :param bot: bot to replace old bot with
 
385
            :type bot: gozerbot.botbase.BotBase
 
386
 
 
387
            .. literalinclude:: ../../gozerbot/fleet.py
 
388
                 :pyobject: Fleet.replace
 
389
 
 
390
        """
 
391
 
 
392
        for i in range(len(self.bots)):
 
393
            if name == self.bots[i].name:
 
394
                self.bots[i] = bot
 
395
                return
 
396
 
 
397
    def initbot(self, bot):
 
398
 
 
399
        """
 
400
            initialise a bot.
 
401
 
 
402
            :param bot: bot to initialise
 
403
            :type bot: gozerbot.botbase.BotBase
 
404
 
 
405
            .. literalinclude:: ../../gozerbot/fleet.py
 
406
                :pyobject: Fleet.initbot
 
407
 
 
408
        """
 
409
 
 
410
        if bot not in self.bots:
 
411
 
 
412
            if not os.path.exists(self.datadir + os.sep + bot.name):
 
413
                os.mkdir(self.datadir + os.sep + bot.name)
 
414
 
 
415
            if type(bot.cfg['owner']) == types.StringType or type(bot.cfg['owner']) == types.UnicodeType:
 
416
                bot.cfg['owner'] = [bot.cfg['owner'], ]
 
417
                bot.cfg.save()
 
418
 
 
419
            users.make_owner(config['owner'] + bot.cfg['owner'])
 
420
            rlog(10, 'fleet', 'added bot: ' + bot.name)
 
421
 
 
422
    @fleetlocked
 
423
    def addbot(self, bot):
 
424
 
 
425
        """
 
426
            add a bot to the fleet .. remove all existing bots with the 
 
427
            same name.
 
428
 
 
429
            :param bot: bot to add
 
430
            :type bot: gozerbot.botbase.BotBase
 
431
            :rtype: boolean
 
432
 
 
433
            .. literalinclude:: ../../gozerbot/fleet.py
 
434
                :pyobject: Fleet.addbot
 
435
            
 
436
        """
 
437
 
 
438
        if bot:
 
439
 
 
440
            for i in range(len(self.bots)-1, -1, -1):
 
441
                if self.bots[i].name == bot.name:
 
442
                    rlog(10, 'fleet', 'removing %s from fleet' % bot.name)
 
443
                    del self.bots[i]
 
444
 
 
445
            rlog(10, 'fleet', 'adding %s' % bot.name)
 
446
            self.bots.append(bot)
 
447
            return True
 
448
 
 
449
        return False
 
450
 
 
451
    def connect(self, name):
 
452
 
 
453
        """
 
454
            connect bot to the server.
 
455
 
 
456
            :param name: name of the bot
 
457
            :type name: string
 
458
            :rtype: boolean
 
459
 
 
460
            .. literalinclude:: ../../gozerbot/fleet.py
 
461
                :pyobject: Fleet.connect
 
462
 
 
463
        """
 
464
 
 
465
        for i in self.bots:
 
466
 
 
467
            if i.name == name:
 
468
                got = i.connect()
 
469
 
 
470
                if got:
 
471
                    start_new_thread(i.joinchannels, ())
 
472
                    return True
 
473
                else:
 
474
                    return False
 
475
 
 
476
    @fleetlocked
 
477
    def delete(self, name):
 
478
 
 
479
        """
 
480
            delete bot with name from fleet.
 
481
 
 
482
            :param name: name of bot to delete
 
483
            :type name: string
 
484
            :rtype: boolean
 
485
 
 
486
            .. literalinclude:: ../../gozerbot/fleet.py
 
487
                :pyobject: Fleet.delete
 
488
 
 
489
        """
 
490
 
 
491
        for i in self.bots:
 
492
 
 
493
            if i.name == name:
 
494
                i.exit()
 
495
                self.remove(i)
 
496
                i.cfg['enable'] = 0
 
497
                i.cfg.save()
 
498
                rlog(10, 'fleet', '%s disabled' % i.name)
 
499
                return True
 
500
 
 
501
        return False
 
502
 
 
503
 
 
504
    def remove(self, bot):
 
505
 
 
506
        """
 
507
            delete bot by object.
 
508
 
 
509
            :param bot: bot to delete
 
510
            :type bot: gozerbot.botbase.BotBase
 
511
            :rtype: boolean
 
512
 
 
513
            .. literalinclude:: ../../gozerbot/fleet.py
 
514
                 :pyobject: Fleet.remove
 
515
 
 
516
        """
 
517
 
 
518
        try:
 
519
            self.bots.remove(bot)
 
520
            return True
 
521
        except ValueError:
 
522
            return False
 
523
 
 
524
    def exit(self, name=None, jabber=False):
 
525
 
 
526
        """
 
527
            call exit on all bots. if jabber=True only jabberbots will exit.
 
528
 
 
529
            :param name: name of the bot to exit. if not provided all bots will exit.
 
530
            :type name: string
 
531
            :param jabber: flag to set when only jabberbots should exit
 
532
            :type jabber: boolean
 
533
            :rtype: boolean
 
534
 
 
535
            .. literalinclude:: ../../gozerbot/fleet.py
 
536
                :pyobject: Fleet.exit
 
537
 
 
538
        """
 
539
        
 
540
        if not name:
 
541
            threads = []
 
542
 
 
543
            for i in self.bots:
 
544
                if jabber and not i.jabber:
 
545
                    pass
 
546
                else:
 
547
                    threads.append(start_new_thread(i.exit, ()))
 
548
 
 
549
            for thr in threads:
 
550
                thr.join()
 
551
 
 
552
            return
 
553
 
 
554
 
 
555
        for i in self.bots:
 
556
 
 
557
            if i.name == name:
 
558
                try:
 
559
                    i.exit()
 
560
                except:
 
561
                    handle_exception()
 
562
                self.remove(i)
 
563
                return True
 
564
 
 
565
        return False
 
566
 
 
567
    def cmnd(self, event, name, cmnd):
 
568
 
 
569
        """
 
570
            do command on a bot.
 
571
 
 
572
            :param event: event to pass on to the dispatcher
 
573
            :type event: gozerbot.event.EventBase
 
574
            :param name: name of the bot to pass on to the dispatcher
 
575
            :type name: string
 
576
            :param cmnd: command to execute on the fleet bot
 
577
            :type cmnd: string
 
578
 
 
579
            .. literalinclude:: ../../gozerbot/fleet.py
 
580
                :pyobject: Fleet.cmnd
 
581
 
 
582
        """
 
583
 
 
584
        bot = self.byname(name)
 
585
 
 
586
        if not bot:
 
587
            return 0
 
588
 
 
589
        from gozerbot.eventbase import EventBase
 
590
        j = plugins.clonedevent(bot, event)
 
591
        j.onlyqueues = True
 
592
        j.txt = cmnd
 
593
        q = Queue.Queue()
 
594
        j.queues = [q]
 
595
        j.speed = 3
 
596
        start_new_thread(plugins.trydispatch, (bot, j))
 
597
        result = waitforqueue(q)
 
598
 
 
599
        if not result:
 
600
            return
 
601
 
 
602
        res = ["<%s>" % bot.name, ]
 
603
        res += result
 
604
        event.reply(res)
 
605
 
 
606
    def cmndall(self, event, cmnd):
 
607
 
 
608
        """
 
609
            do a command on all bots.
 
610
 
 
611
            :param event: event to pass on to dispatcher
 
612
            :type event: gozerbot.eventbase.EventBase
 
613
            :param cmnd: the command string to execute
 
614
            :type cmnd: string
 
615
 
 
616
            .. literalinclude:: ../../gozerbot/fleet.py
 
617
                :pyobject: Fleet.cmndall
 
618
 
 
619
        """
 
620
 
 
621
        threads = []
 
622
 
 
623
        for i in self.bots:
 
624
            thread = start_new_thread(self.cmnd, (event, i.name, cmnd))
 
625
            threads.append(thread)
 
626
 
 
627
        for i in threads:
 
628
            i.join()
 
629
 
 
630
    def broadcast(self, txt):
 
631
 
 
632
        """
 
633
            broadcast txt to all bots.
 
634
 
 
635
            :param txt: text to broadcast on all bots
 
636
            :type txt: string
 
637
 
 
638
            .. literalinclude:: ../../gozerbot/fleet.py
 
639
                :pyobject: Fleet.broadcast
 
640
 
 
641
        """
 
642
 
 
643
        for i in self.bots:
 
644
            i.broadcast(txt)
 
645
 
 
646
# ============
 
647
# INIT SECTION
 
648
 
 
649
 
 
650
# main fleet object
 
651
fleet = Fleet()
 
652
 
 
653
# END INIT
 
654
# ========