~erica-wolfe/systers/wishlist

« back to all changes in this revision

Viewing changes to Mailman/PsqlMemberships.py

  • Committer: Jennifer Redman
  • Author(s): Malveeka_Tewari
  • Date: 2009-08-04 17:07:29 UTC
  • mfrom: (68.4.7 auth)
  • Revision ID: jenred@cherimoya.groveronline.com-20090804170729-3uslf2100b8shdie
Merge with sql Membership Adaptor

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import time, sys
 
2
from types import StringType
 
3
from Mailman.Logging.Syslog import syslog
 
4
 
 
5
from Mailman import mm_cfg
 
6
from Mailman import Utils
 
7
from Mailman import Errors
 
8
from Mailman import MemberAdaptor
 
9
from Mailman.Bouncer import _BounceInfo
 
10
 
 
11
import psycopg2
 
12
 
 
13
ISREGULAR = 1
 
14
ISDIGEST = 2
 
15
tm_min = 4
 
16
 
 
17
mm_cfg.connection = 0
 
18
mm_cfg.cursor = 0
 
19
 
 
20
try:
 
21
    mm_cfg.PGSQL_MEMBER_DB_VERBOSE
 
22
except AttributeError:
 
23
    mm_cfg.PGSQL_MEMBER_DB_VERBOSE = False  # default value
 
24
    pass
 
25
 
 
26
 
 
27
class PgsqlMemberships(MemberAdaptor.MemberAdaptor):
 
28
 
 
29
    def __init__(self, mlist):
 
30
        self.__mlist = mlist
 
31
        self._dbconnect()
 
32
 
 
33
        # define the table and standard condition reflecting listname
 
34
        # (this is for upwards compatibility with the 'wide' mode and
 
35
        # the formerly fixed name of database table in 'flat' mode)
 
36
        if self.getTableType() is 'flat':
 
37
            try:
 
38
                self._table = mm_cfg.PGSQL_MEMBER_TABLE_NAME
 
39
            except AttributeError:
 
40
                self._table = 'mailman_pgsql'
 
41
            self._where = "listname = '%s'" %(self.__mlist.internal_name())
 
42
        else:
 
43
            self._table = self.__mlist.internal_name()
 
44
            self._where = '1=1'
 
45
 
 
46
        # Make sure we always have the table we need...
 
47
        # if mm_cfg.PGSQL_MEMBER_CREATE_TABLE was defined
 
48
        try:
 
49
            if mm_cfg.PGSQL_MEMBER_CREATE_TABLE:
 
50
                self.createTable()
 
51
        except AttributeError:
 
52
            pass
 
53
 
 
54
        if mm_cfg.PGSQL_MEMBER_DB_VERBOSE:
 
55
            # Message to indicate successful init.
 
56
            message = "PgsqlMemberships " \
 
57
                + "initialized with host: %s (%s)" % (
 
58
                mm_cfg.connection.get_host_info(),
 
59
                mm_cfg.connection.get_server_info() )
 
60
            syslog('error', message)
 
61
            syslog('pgsql', message)
 
62
 
 
63
        # add a cache memory
 
64
        self._cache = {}
 
65
        self._cachedate = 0
 
66
 
 
67
 
 
68
    def __del__(self):
 
69
        # Cleaning up
 
70
        try:
 
71
            mm_cfg.cursor.close()
 
72
        except:
 
73
            pass
 
74
        try:
 
75
            mm_cfg.connection.close()
 
76
        except:
 
77
            pass
 
78
        if mm_cfg.PGSQL_MEMBER_DB_VERBOSE:
 
79
            # Message to indicate successful close.
 
80
            syslog("error", "PgsqlMemberships unloaded" )
 
81
            syslog("pgsql", "PgsqlMemberships unloaded" )
 
82
 
 
83
    # Find out whether we should be using 'flat' or 'wide' table type.
 
84
    # for backwards compatibility, the default is 'wide'
 
85
    def getTableType(me):
 
86
        if mm_cfg.PGSQL_MEMBER_TABLE_TYPE:
 
87
            if mm_cfg.PGSQL_MEMBER_TABLE_TYPE is 'flat':
 
88
                return 'flat'
 
89
            else:
 
90
                return 'wide'
 
91
        else:
 
92
            return 'wide'
 
93
 
 
94
    # Check to see if a connection's still alive. If not, reconnect.
 
95
    def _dbconnect(self):
 
96
        if mm_cfg.connection:
 
97
            try:
 
98
                if mm_cfg.connection.ping() == 0:
 
99
                    return mm_cfg.connection
 
100
            except:
 
101
                syslog('pgsql', 'connection warning')
 
102
                pass
 
103
 
 
104
            # Connection failed, or an error, try a hard dis+reconnect.
 
105
            try:
 
106
                mm_cfg.cursor.close()
 
107
            except:
 
108
                syslog('error', 'error on mm_cfg.cursor.close()')
 
109
                pass
 
110
 
 
111
            try:
 
112
                mm_cfg.connection.close()
 
113
            except:
 
114
                syslog('error', 'error on mm_cfg.connection.close()')
 
115
                pass
 
116
 
 
117
        try:
 
118
            mm_cfg.connection = psycopg2.connect(
 
119
                passwd=mm_cfg.PGSQL_MEMBER_DB_PASS,
 
120
                db=mm_cfg.PGSQL_MEMBER_DB_NAME,
 
121
                user=mm_cfg.PGSQL_MEMBER_DB_USER,
 
122
                host=mm_cfg.PGSQL_MEMBER_DB_HOST)
 
123
            mm_cfg.cursor = mm_cfg.connection.cursor()
 
124
        except pysopg.OperationalError, e:
 
125
            message = "Error connecting to PGSQL database %s (%s): %s" %(
 
126
                    mm_cfg.PGSQL_MEMBER_DB_NAME, e.args[0], e.args[1])
 
127
            syslog('error', message)
 
128
            if mm_cfg.PGSQL_MEMBER_DB_VERBOSE:
 
129
                syslog('pgsql', message)
 
130
            # exit? why not sleep(30) and retry?
 
131
            sys.exit(1)
 
132
 
 
133
        return mm_cfg.connection
 
134
 
 
135
 
 
136
 
 
137
    # create tables (if the option is set in mm_cfg)
 
138
    def createTable(self):
 
139
        if self.getTableType() is 'flat':
 
140
            self.query (
 
141
"""CREATE TABLE IF NOT EXISTS `%s` (
 
142
  listname varchar(100) NOT NULL,
 
143
  address varchar(255) NOT NULL,
 
144
  hide enum('Y','N') NOT NULL default 'N',
 
145
  nomail enum('Y','N') NOT NULL default 'N',
 
146
  ack enum('Y','N') NOT NULL default 'Y',
 
147
  not_metoo enum('Y','N') NOT NULL default 'Y',
 
148
  digest enum('Y','N') NOT NULL default 'N',
 
149
  plain enum('Y','N') NOT NULL default 'N',
 
150
  password varchar(255) NOT NULL default '!',
 
151
  lang varchar(255) NOT NULL default 'en',
 
152
  name varchar(255) default NULL,
 
153
  one_last_digest enum('Y','N') NOT NULL default 'N',
 
154
  user_options bigint(20) NOT NULL default 0,
 
155
  delivery_status INT(10) NOT NULL default 0,
 
156
  topics_userinterest varchar(255) default NULL,
 
157
  delivery_status_timestamp datetime default '0000-00-00 00:00:00',
 
158
  bi_cookie varchar(255) default NULL,
 
159
  bi_score double NOT NULL default '0',
 
160
  bi_noticesleft double NOT NULL default '0',
 
161
  bi_lastnotice date NOT NULL default '0000-00-00',
 
162
  bi_date date NOT NULL default '0000-00-00',
 
163
  PRIMARY KEY  (listname, address)
 
164
) ENGINE=MyISAM""" %(self._table))
 
165
        else:
 
166
            self.query (
 
167
"""CREATE TABLE IF NOT EXISTS `%s` (
 
168
  address varchar(255) NOT NULL,
 
169
  hide enum('Y','N') NOT NULL default 'N',
 
170
  nomail enum('Y','N') NOT NULL default 'N',
 
171
  ack enum('Y','N') NOT NULL default 'Y',
 
172
  not_metoo enum('Y','N') NOT NULL default 'Y',
 
173
  digest enum('Y','N') NOT NULL default 'N',
 
174
  plain enum('Y','N') NOT NULL default 'N',
 
175
  password varchar(255) NOT NULL default '!',
 
176
  lang varchar(255) NOT NULL default 'en',
 
177
  name varchar(255) default NULL,
 
178
  one_last_digest enum('Y','N') NOT NULL default 'N',
 
179
  user_options bigint(20) NOT NULL default 0,
 
180
  delivery_status int(10) NOT NULL default 0,
 
181
  topics_userinterest varchar(255) default NULL,
 
182
  delivery_status_timestamp datetime DEFAULT '0000-00-00 00:00:00',
 
183
  bi_cookie varchar(255) default NULL,
 
184
  bi_score double NOT NULL default '0',
 
185
  bi_noticesleft double NOT NULL default '0',
 
186
  bi_lastnotice date NOT NULL default '0000-00-00',
 
187
  bi_date date NOT NULL default '0000-00-00',
 
188
  PRIMARY KEY  (address)
 
189
) ENGINE=MyISAM""" %(self._table))
 
190
 
 
191
 
 
192
    # empty the cache (when we touch a value)
 
193
    def uncache(self):
 
194
        self._cache = {}
 
195
        self._cachedate = time.localtime()[tm_min]
 
196
 
 
197
    # Apply query on list (manages both 'flat' and 'wide' modes)
 
198
    def query(self, query):
 
199
        if mm_cfg.PGSQL_MEMBER_DB_VERBOSE:
 
200
            syslog('pgsql', query)
 
201
        self._dbconnect()
 
202
        return mm_cfg.cursor.execute (query)
 
203
 
 
204
    # return all members according to a certain condition
 
205
    def queryall(self, query, cache=False):
 
206
        self.query(query)
 
207
        # get the number of rows in the resultset
 
208
        numrows = int(mm_cfg.cursor.rowcount)
 
209
        # save one at a time
 
210
        results = []
 
211
        for x in range(0,numrows):
 
212
            row = mm_cfg.cursor.fetchone()
 
213
            results.append(row[0])
 
214
            # we don't want to cache the whole list for global requests
 
215
            if cache and numrows < 1000:
 
216
                self._cache[row[1]] = row[2:]
 
217
        return results
 
218
 
 
219
    # select *, cache it, then return only the field that's asked for
 
220
    def select(self, what, where=''):
 
221
        query = "SELECT " + what \
 
222
            + ",address,name,user_options,delivery_status,lang,digest " \
 
223
            + "FROM `%s` WHERE %s" %(self._table, self._where)
 
224
        if where:
 
225
            query += " AND %s" %(where)
 
226
        return self.queryall(query + ' ORDER BY address', True)
 
227
 
 
228
    def select_on(self, what, address):
 
229
        if self._cachedate != time.localtime()[tm_min]:
 
230
            self.uncache()
 
231
        try:
 
232
            a = self._cache[address]
 
233
            if what == 'name':
 
234
                num = 0
 
235
            elif what == 'user_options':
 
236
                num = 1
 
237
            elif what == 'delivery_status':
 
238
                num = 2
 
239
            elif what == 'lang':
 
240
                num = 3
 
241
            elif what == 'digest':
 
242
                num = 4
 
243
            a = [ a[num] ]
 
244
        except:
 
245
            a = self.select(what,
 
246
                "address='%s'" %(self.escape(address)))
 
247
        return a
 
248
 
 
249
    def update_on(self, what, value, address):
 
250
        if what == 'delivery_status':
 
251
            dst = ", delivery_status_timestamp=NOW() "
 
252
        else:
 
253
            dst = ""
 
254
        self.query("UPDATE `%s` " %(self._table)
 
255
                + ("SET %s = '%s' " %(what, self.escape(value))
 
256
                + dst
 
257
                + ("WHERE %s " %(self._where))
 
258
                + ("AND address = '%s'" %(self.escape(address)))))
 
259
        # remove the cache
 
260
        self.uncache()
 
261
 
 
262
    def escape(self, value):
 
263
        # transforms accents into html entities (&#233;)
 
264
        # TODO: find out which language is current (here: assumes iso-8859-1)
 
265
        value = Utils.uncanonstr(value)
 
266
 
 
267
        # add slashes before " and '
 
268
        return psycopg2.escape_string(value)
 
269
 
 
270
 
 
271
    ############################### Now the active codes #######
 
272
    #
 
273
    # Read interface
 
274
    #
 
275
 
 
276
    # All members
 
277
    def getMembers(self):
 
278
        return self.select('address')
 
279
 
 
280
    # regular members
 
281
    def getRegularMemberKeys(self):
 
282
        return self.select('address', "digest = 'N'")
 
283
 
 
284
    # digest members
 
285
    def getDigestMemberKeys(self):
 
286
        return self.select('address', "digest = 'Y'")
 
287
 
 
288
    # status (regular/digest) of a member (returns a key - lowercase)
 
289
    def __get_cp_member(self, member):
 
290
        lcmember = member.lower()
 
291
        digest = self.select_on('digest', lcmember)
 
292
        if len(digest):
 
293
            if digest is 'Y':
 
294
                return lcmember, ISDIGEST
 
295
            else:
 
296
                return lcmember, ISREGULAR
 
297
        return None, None
 
298
 
 
299
    # is she a member?
 
300
    def isMember(self, member):
 
301
        member = self.select_on('name', member)
 
302
        if len(member):
 
303
            return 1
 
304
        return 0
 
305
 
 
306
    # Covered by SQL __get_cp_member()
 
307
    def getMemberKey(self, member):
 
308
        cpaddr, where = self.__get_cp_member(member)
 
309
        if cpaddr is None:
 
310
            raise Errors.NotAMemberError, member
 
311
        return member.lower()
 
312
 
 
313
    # Covered by SQL __get_cp_member()
 
314
    def getMemberCPAddress(self, member):
 
315
        cpaddr, where = self.__get_cp_member(member)
 
316
        if cpaddr is None:
 
317
            raise Errors.NotAMemberError, member
 
318
        return cpaddr
 
319
 
 
320
    # Covered by SQL __get_cp_member()
 
321
    def getMemberCPAddresses(self, members):
 
322
        return [self.__get_cp_member(member)[0] for member in members]
 
323
 
 
324
    # SELECT password FROM <listname> WHERE address = member.lower()
 
325
    def getMemberPassword(self, member):
 
326
        password = self.select_on('password', member)
 
327
        if len(password):
 
328
            return password[0]
 
329
        else:
 
330
            raise Errors.NotAMemberError, member
 
331
 
 
332
    # Covered by SQL getMemberPassword()
 
333
    def authenticateMember(self, member, response):
 
334
        secret = self.getMemberPassword(member)
 
335
        if secret == response:
 
336
            return secret
 
337
        return 0
 
338
 
 
339
    # Covered by SQL isMember()
 
340
    def __assertIsMember(self, member):
 
341
        if not self.isMember(member):
 
342
            raise Errors.NotAMemberError, member
 
343
 
 
344
    def getMemberLanguage(self, member):
 
345
        lang = self.select_on('lang',member)
 
346
        if len(lang) and lang[0] in self.__mlist.GetAvailableLanguages():
 
347
            return lang[0]
 
348
        else:
 
349
            return self.__mlist.preferred_language
 
350
 
 
351
    # getOptions: different methods for digest and other (bitfield) options
 
352
    def getMemberOption(self, member, flag):
 
353
        self.__assertIsMember(member)
 
354
        if flag == mm_cfg.Digests:
 
355
            cpaddr, where = self.__get_cp_member(member)
 
356
            return where == ISDIGEST
 
357
        options = self.select_on('user_options', member)
 
358
        if len(options):
 
359
            return not not (options[0] & flag)
 
360
 
 
361
 
 
362
    # new method to gest faster results when searching a user in the admin Gui
 
363
    def getMembersMatching(self, regexp):
 
364
        return self.select('address',
 
365
            "(address REGEXP '%s' OR name REGEXP '%s')"
 
366
            %( self.escape(regexp), self.escape(regexp) ) )
 
367
 
 
368
    # new method to get faster results when querying the number of subscribers
 
369
    def getMembersCount(self, reason=None):
 
370
        if reason:
 
371
            where = " AND digest='%s'" %reason
 
372
        else:
 
373
            where = ""
 
374
        self.query("SELECT COUNT(*) FROM `%s` WHERE %s%s" %(
 
375
          self._table, self._where, where))
 
376
        count = mm_cfg.cursor.fetchone()
 
377
        return int(count[0])
 
378
 
 
379
    # get member's name (slow method if you need many)
 
380
    # due to the way escape() is built, names are stored in html
 
381
    # format in the DB, hence the canonstr() to put them back to
 
382
    # normal (TODO)
 
383
    def getMemberName(self, member):
 
384
        name = self.select_on('name', member)
 
385
        if len(name):
 
386
          try:
 
387
            return Utils.canonstr(name[0])
 
388
          except:
 
389
            return name[0]
 
390
        self.__assertIsMember(member)
 
391
 
 
392
    # topics
 
393
    def getMemberTopics(self, member):
 
394
        topics = self.select_on('topics_userinterest',member)
 
395
        if len(topics) and isinstance(topics[0], str):
 
396
            return topics[0].split(',')
 
397
        return []
 
398
        self.__assertIsMember(member)
 
399
 
 
400
    # delivery status
 
401
    def getDeliveryStatus(self, member):
 
402
        status = self.select_on('delivery_status',member)
 
403
        if len(status):
 
404
            if status[0] in (MemberAdaptor.ENABLED, MemberAdaptor.UNKNOWN,
 
405
                          MemberAdaptor.BYUSER, MemberAdaptor.BYADMIN,
 
406
                          MemberAdaptor.BYBOUNCE):
 
407
                return status[0]
 
408
            else:
 
409
                return MemberAdaptor.ENABLED
 
410
        self.__assertIsMember(member)
 
411
 
 
412
 
 
413
    # delivery status change time
 
414
    def getDeliveryStatusChangeTime(self, member):
 
415
        time = self.select_on('delivery_status_timestamp',member)
 
416
        if len(time):
 
417
            time = time[0]
 
418
            if time is '0':
 
419
                return MemberAdaptor.ENABLED
 
420
            else:
 
421
                return time
 
422
        self.__assertIsMember(member)
 
423
 
 
424
    # Covered by SQL getMembers(), and getDeliveryStatus().
 
425
    def getDeliveryStatusMembers(self, status=(MemberAdaptor.UNKNOWN,
 
426
                                               MemberAdaptor.BYUSER,
 
427
                                               MemberAdaptor.BYADMIN,
 
428
                                               MemberAdaptor.BYBOUNCE)):
 
429
        return [member for member in self.getMembers()
 
430
                if self.getDeliveryStatus(member) in status]
 
431
 
 
432
    # show bouncing members
 
433
    def getBouncingMembers(self):
 
434
        self.query("""SELECT bi_cookie,bi_score,bi_noticesleft,
 
435
            UNIX_TIMESTAMP(bi_lastnotice),UNIX_TIMESTAMP(bi_date),address
 
436
            FROM `%s` WHERE %s""" %(self._table, self._where))
 
437
        # get the number of rows in the resultset
 
438
        numrows = int(mm_cfg.cursor.rowcount)
 
439
        # save one address at a time
 
440
        bounce_info_list = []
 
441
        for x in range(0,numrows):
 
442
            row = mm_cfg.cursor.fetchone()
 
443
            # We must not return anything if there is
 
444
            # no bounce info for that member to start with.
 
445
            if row[4] > 0:
 
446
                # Append the member name to the bounce info list.
 
447
                bounce_info_list.append(row[5])
 
448
        return [member.lower() for member in bounce_info_list]
 
449
 
 
450
    def getBounceInfo(self, member):
 
451
        self.query("""SELECT
 
452
            bi_score,
 
453
            bi_noticesleft,
 
454
            YEAR(bi_lastnotice),
 
455
            MONTH(bi_lastnotice),
 
456
            DAYOFMONTH(bi_lastnotice),
 
457
            YEAR(bi_date),
 
458
            MONTH(bi_date),
 
459
            DAYOFMONTH(bi_date),
 
460
            bi_cookie
 
461
            FROM `%s` WHERE %s AND """ %(self._table, self._where)
 
462
            + ("address = '%s'" %( self.escape(member) ) ))
 
463
        numrows = int(mm_cfg.cursor.rowcount)
 
464
        if numrows is 0:
 
465
            self.__assertIsMember(member)
 
466
        row = mm_cfg.cursor.fetchone()
 
467
        
 
468
        if row[3] <= 0:
 
469
            return None;
 
470
        # Otherwise, populate a bounce_info structure.
 
471
        bounce_info = _BounceInfo(member, row[0],
 
472
            (row[5],row[6],row[7]), row[1])
 
473
        bounce_info.lastnotice = (row[2],row[3],row[4])
 
474
        bounce_info.cookie = row[8]
 
475
        return bounce_info
 
476
 
 
477
 
 
478
    #
 
479
    # Write interface
 
480
    #
 
481
    def addNewMember(self, member, **kws):
 
482
#        assert self.__mlist.Locked()
 
483
        # Make sure this address isn't already a member
 
484
        if self.isMember(member):
 
485
            raise Errors.MMAlreadyAMember, member
 
486
        # Parse the keywords
 
487
        digest = 0
 
488
        password = Utils.MakeRandomPassword()
 
489
        language = self.__mlist.preferred_language
 
490
        realname = None
 
491
        if kws.has_key('digest'):
 
492
            digest = kws['digest']
 
493
            del kws['digest']
 
494
        if kws.has_key('password'):
 
495
            password = kws['password']
 
496
            del kws['password']
 
497
        if kws.has_key('language'):
 
498
            language = kws['language']
 
499
            del kws['language']
 
500
        if kws.has_key('realname'):
 
501
            realname = kws['realname']
 
502
            del kws['realname']
 
503
        # Assert that no other keywords are present
 
504
        if kws:
 
505
            raise ValueError, kws.keys()
 
506
        # If the localpart has uppercase letters in it, then the value in the
 
507
        # members (or digest_members) dict is the case preserved address.
 
508
        # Otherwise the value is 0.  Note that the case of the domain part is
 
509
        # of course ignored.
 
510
        if Utils.LCDomain(member) == member.lower():
 
511
            value = 0
 
512
        else:
 
513
            value = member
 
514
            member = member.lower()
 
515
        if digest:
 
516
            digest = 'Y'
 
517
        else:
 
518
            digest = 'N'
 
519
        # All we need to do here is add the address.
 
520
        # and Set the member's default set of options
 
521
        if self.__mlist.new_member_options:
 
522
            options = self.__mlist.new_member_options
 
523
        else:
 
524
            options = 0
 
525
        if self.getTableType() is 'flat':
 
526
            query = "INSERT INTO `%s` " %(self._table) \
 
527
            + "(listname, address, user_options, password, lang, " \
 
528
            + "digest, delivery_status) values " \
 
529
            + "('%s','%s',%s,'%s','%s','%s','%s')"
 
530
        else:
 
531
            query = "INSERT INTO `%s` " \
 
532
            + "(address, user_options, password, lang, " \
 
533
            + "digest, delivery_status) values " \
 
534
            + "('%s',%s,'%s','%s','%s','%s')"
 
535
        query = query %( self.__mlist.internal_name(),
 
536
            self.escape(member), options, password,
 
537
            language, digest, MemberAdaptor.ENABLED)
 
538
        if mm_cfg.PGSQL_MEMBER_DB_VERBOSE:
 
539
            syslog('pgsql',query)
 
540
        mm_cfg.cursor.execute(query)
 
541
        if realname:
 
542
            self.setMemberName(member, realname)
 
543
 
 
544
    def removeMember(self, member):
 
545
#        assert self.__mlist.Locked()
 
546
        self.__assertIsMember(member)
 
547
        self.query("DELETE FROM `%s` WHERE %s " %(self._table, self._where)
 
548
            + ("AND address = '%s'" %( self.escape(member.lower()) ) ))
 
549
        self.uncache()
 
550
 
 
551
    def changeMemberAddress(self, member, newaddress, nodelete=0):
 
552
#        assert self.__mlist.Locked()
 
553
        # Make sure this address isn't already a member
 
554
        if self.isMember(newaddress):
 
555
            raise Errors.MMAlreadyAMember, newaddress
 
556
        self.update_on('address', newaddress, member)
 
557
 
 
558
    def setMemberPassword(self, member, password):
 
559
#        assert self.__mlist.Locked()
 
560
        self.update_on('password', password, member)
 
561
 
 
562
    def setMemberLanguage(self, member, lang):
 
563
#        assert self.__mlist.Locked()
 
564
        self.update_on('lang', lang, member)
 
565
 
 
566
    def setMemberOption(self, member, flag, value):
 
567
#        assert self.__mlist.Locked()
 
568
        if flag == mm_cfg.Digests:
 
569
            if value:
 
570
                # Be sure the list supports digest delivery
 
571
                if not self.__mlist.digestable:
 
572
                    raise Errors.CantDigestError
 
573
                # The user is turning on digest mode
 
574
                # If they are already receiving digests, report an error.
 
575
                if self.getMemberOption(member, mm_cfg.Digests) is 'Y':
 
576
                    raise Errors.AlreadyReceivingDigests, member
 
577
                # If we've got past all this, actually turn on digest mode.
 
578
                self.update_on('digest', 'Y', member)
 
579
            else:
 
580
                # Be sure the list supports regular delivery
 
581
                if not self.__mlist.nondigestable:
 
582
                    raise Errors.MustDigestError
 
583
                # The user is turning off digest mode
 
584
                # If they are already receiving regular, report an error.
 
585
                if self.getMemberOption(member, mm_cfg.Digests) is 'N':
 
586
                    raise Errors.AlreadyReceivingRegularDeliveries, member
 
587
                # If we've got past all this, actually turn off digest mode.
 
588
                self.update_on('digest', 'N', member)
 
589
            return
 
590
 
 
591
        if value:
 
592
            self.query("UPDATE `%s` " %(self._table)
 
593
                + ("SET user_options = user_options | %s " %(flag))
 
594
                + "WHERE %s " %(self._where)
 
595
                + ("AND address = '%s'" %( self.escape(member) ) ))
 
596
        else:
 
597
            self.query("UPDATE `%s` " %(self._table)
 
598
                + ("SET user_options = user_options & ~%s " %(flag))
 
599
                + "WHERE %s " %(self._where)
 
600
                + ("AND address = '%s'" %( self.escape(member) ) ))
 
601
        # remove the cache
 
602
        self.uncache()
 
603
 
 
604
    def setMemberName(self, member, name):
 
605
#        assert self.__mlist.Locked()
 
606
        self.update_on('name', name, member)
 
607
 
 
608
    def setMemberTopics(self, member, topics):
 
609
#        assert self.__mlist.Locked()
 
610
        if isinstance(topics,list):
 
611
          topics=",".join(topics)
 
612
        else:
 
613
          topics=""
 
614
        self.query("UPDATE `%s` " %(self._table)
 
615
            + ("SET topics_userinterest = '%s' " %(
 
616
              self.escape(topics) ))
 
617
            + "WHERE %s " %(self._where)
 
618
            + ("AND address = '%s'" %( self.escape(member) )))
 
619
 
 
620
    def setDeliveryStatus(self, member, status):
 
621
        assert status in (MemberAdaptor.ENABLED,  MemberAdaptor.UNKNOWN,
 
622
                          MemberAdaptor.BYUSER,   MemberAdaptor.BYADMIN,
 
623
                          MemberAdaptor.BYBOUNCE)
 
624
#        assert self.__mlist.Locked()
 
625
        member = member.lower()
 
626
        if status == MemberAdaptor.ENABLED:
 
627
            # Enable by resetting their bounce info.
 
628
            self.setBounceInfo(member, None)
 
629
        else:
 
630
            self.query("UPDATE `%s` " %(self._table)
 
631
                + ("SET delivery_status = '%s', " %(status))
 
632
                + "delivery_status_timestamp=NOW() WHERE %s " %(self._where)
 
633
                + ("AND address = '%s'" %( self.escape(member) )))
 
634
        # remove the cache
 
635
        self.uncache()                                                
 
636
 
 
637
    def setBounceInfo(self, member, info):
 
638
#        assert self.__mlist.Locked()
 
639
        member = member.lower()
 
640
        if info is None:
 
641
            self.query("UPDATE `%s` " %(self._table)
 
642
                + ("SET delivery_status = '%s', " %(MemberAdaptor.ENABLED))
 
643
                + "bi_cookie = NULL, "
 
644
                + "bi_score = 0, "
 
645
                + "bi_noticesleft = 0, "
 
646
                + "bi_lastnotice = '0000-00-00', "
 
647
                + "bi_date = '0000-00-00' "
 
648
                + "WHERE %s " %(self._where)
 
649
                + ("AND address = '%s'" %( self.escape(member) )))
 
650
        else:
 
651
            lnsql = time.strftime("%Y-%m-%d", time.strptime('-'.join(map(str,info.lastnotice)),'%Y-%m-%d'))
 
652
            datesql = time.strftime("%Y-%m-%d", time.strptime('-'.join(map(str,info.date)),'%Y-%m-%d'))
 
653
            self.query("UPDATE `%s` " %(self._table)
 
654
                + (("SET bi_cookie = '%s', "
 
655
                    + "bi_score = %s, "
 
656
                    + "bi_noticesleft = %s, "
 
657
                    + "bi_lastnotice = '%s', "
 
658
                    + "bi_date = '%s' ") %(
 
659
                        info.cookie, info.score,
 
660
                        info.noticesleft, lnsql, datesql
 
661
                    ))
 
662
                + ("WHERE %s " %(self._where))
 
663
                + ("AND address = '%s'" %( self.escape(member) )))
 
664
 
 
665
 
 
666
 
 
667
# this function can be plugged into Mailman.MailList.Save
 
668
# it saves a copy of a few list's attributes into a database
 
669
def SaveToDb(self,dict):
 
670
    query = 'REPLACE lists (listname,moderation,advertised,new_member_options,subscribe_policy,host_name,description,info,count) ' \
 
671
        " VALUES ('%s','%s','%s','%s','%s','%s','%s','%s','%s')" % (\
 
672
        self.internal_name(), \
 
673
        self.default_member_moderation, \
 
674
        self.advertised, \
 
675
        self.new_member_options, \
 
676
        self.subscribe_policy, \
 
677
        self.host_name, \
 
678
        self.escape(self.description), \
 
679
        self.escape(self.info), \
 
680
        self.getMembersCount() \
 
681
        )
 
682
    syslog('pgsql', query)
 
683
    try:
 
684
        self.query(query)
 
685
    except Exception,e:
 
686
        syslog('pgsql', 'error %s'%e)
 
687
    pass