~systers-dev/systers/development

« back to all changes in this revision

Viewing changes to Mailman/DlistUtils.py

  • Committer: Jennifer Redman
  • Date: 2009-11-11 02:07:48 UTC
  • mfrom: (72.1.18 orm_objectoriented)
  • Revision ID: jenred@cherimoya.groveronline.com-20091111020748-2v0azbw3aklqtr1e
Updated development with latest orm/wishlist_branch fixes

Show diffs side-by-side

added added

removed removed

Lines of Context:
33
33
#2)The case when we declare one object say subscriber = Subscriber(mlist) and then use this subscriber object many times to
34
34
#call different decorated member functions of Subscriber class.Such kind of case will always satisfy self ==  classObject,
35
35
#here we want store.close() to take place as if it does not,it will leave the store connection always open that can possibly be a bug.
36
 
 
 
36
 
37
37
def decfunc(f):
38
 
 
39
 
        def inner(self, *args):
40
 
                global classObject
41
 
                if self == classObject:
42
 
                        if self.store == None:#for the case when same object is used for calling more than one functions
43
 
                                self.store = Store(self.database) 
44
 
                                result = f(self,*args)
45
 
                                self.store.commit()
46
 
                                self.store.close()
47
 
                                self.store = None
48
 
                        else:#To handle the nesting issue 
49
 
                                result = f(self,*args) 
50
 
                                self.store.commit()
51
 
                else:#for the case whenever a new object calls its decorated member function 
52
 
                        classObject = self
53
 
                        self.store = Store(self.database)
54
 
                        result = f(self,*args)
55
 
                        self.store.commit()
56
 
                        self.store.close()
57
 
                        self.store = None
58
 
                return result
59
 
        return inner
 
38
    def inner(self, *args):
 
39
        global classObject
 
40
        if self == classObject:
 
41
            if self.store == None:#for the case when same object is used for calling more than one functions
 
42
                self.store = Store(self.database)
 
43
                result = f(self,*args)
 
44
                self.store.commit()
 
45
                self.store.close()
 
46
                self.store = None
 
47
            else:#To handle the nesting issue
 
48
                result = f(self,*args)
 
49
                self.store.commit()
 
50
        else:#for the case whenever a new object calls its decorated member function
 
51
            classObject = self
 
52
            self.store = Store(self.database)
 
53
            result = f(self,*args)
 
54
            self.store.commit()
 
55
            self.store.close()
 
56
            self.store = None
 
57
        return result
 
58
    return inner
60
59
 
61
60
#Define all the classes corresponding to the tables in the database
62
61
class Subscriber(object):
63
 
        __storm_table__ = "subscriber"
64
 
        subscriber_id = Int(primary = True,default = AutoReload)
65
 
        mailman_key = Unicode()
66
 
        preference = Int(default = 1)
67
 
        format = Int(default = 3)
68
 
        deleted = Bool(default = False)
69
 
        suppress = Int(default = 0)
70
 
 
71
 
        def __init__(self,mlist):
72
 
                self.mlist = mlist
73
 
                self.database = getConn(mlist)
74
 
 
75
 
        @decfunc
76
 
        def getSubscriber_id_raw(self, addr):
77
 
                if addr == None:
78
 
                        return None
79
 
 
80
 
                command = "result = self.store.find(Subscriber,Subscriber.mailman_key == unicode(addr.lower(),'utf-8'))\na = [(subscriber.subscriber_id) for subscriber in result]\n"
81
 
                if DEBUG_MODE:
82
 
                        syslog('info', "DlistUtils:(getSubbscriber_id_raw)executing query:\n%s", command)
83
 
                #storm recognizes unicode only
84
 
                result = self.store.find(Subscriber,Subscriber.mailman_key == unicode(addr.lower(),"utf-8")) 
85
 
                a = [(subscriber.subscriber_id) for subscriber in result]
86
 
                if DEBUG_MODE:
87
 
                        syslog('info', 'value of a is: %s\n', a)
88
 
 
89
 
                #The ResultSet "a" obtained in above storm command and later on in similar ones is always a list
90
 
                if a == []:
91
 
                        return None 
92
 
                else:
93
 
                        return a[0]
94
 
 
95
 
        def getSubscriber_id_raw_or_die(self, addr):
96
 
                result = self.getSubscriber_id_raw(addr)
97
 
                if (result == None):
98
 
                    #syslog('error', 'getSubscriber_id_raw_or_die /msg nickserv register <your-password> <your-email>unable to find address /%s/ for mailing list /%s/', addr, self.mlist.internal_name())
99
 
                    raise ErrorsDlist.InternalError
100
 
                else:
101
 
                    return result
102
 
 
103
 
        def getSubscriber_id(self, msg, msgdata, safe=0, loose=0):
104
 
                """Returns the subscriber_id of the sender of a message and sets the 'subscriber_id' field in the msg object."""
105
 
                fromAddr = email.Utils.parseaddr(msg['From'])[1]
106
 
                try:
107
 
                        subscriber_id = msgdata['subscriber_id']
108
 
                        return subscriber_id
109
 
                except:
110
 
                        subscriber_id = self.getSubscriber_id_raw(fromAddr)
111
 
                        if subscriber_id == None:
112
 
                            alias = Alias(self.mlist)
113
 
                            bestAddr = alias.canonicalize_sender(msg.get_senders())
114
 
                            subscriber_id = self.getSubscriber_id_raw(bestAddr)
115
 
                            if subscriber_id == None:
116
 
                                #if DEBUG_MODE:
117
 
                                        #syslog('info', "DlistUtils.getSubscriber_id: subscriber_id is None and safe is %d", safe)
118
 
                                if safe:
119
 
                                    # This could happen if a non-member is given permission to post
120
 
                                    return 0
121
 
                                else:
122
 
                                    #if DEBUG_MODE:
123
 
                                        #syslog('info', "Raising ErrorsDlist.NotMemberError")
124
 
                                    raise ErrorsDlist.NotMemberError("Your request could not be processed because %s is not subscribed to %s.  Perhaps you are subscribed with a different email address, which forwards to %s.  If so, please log into %s and add this as an 'other incoming email address'." % (fromAddr, self.mlist.internal_name(), fromAddr, self.mlist.GetScriptURL('options', 1))) 
125
 
                        if DEBUG_MODE:
126
 
                                syslog('info', 'subscriber_id = %d\n', subscriber_id)
127
 
                        msgdata['subscriber_id'] = subscriber_id
128
 
                        return subscriber_id
129
 
 
130
 
        @decfunc
131
 
        def get_format(self, subscriber_id):            
132
 
                command = "return int(self.store.get(Subscriber,subscriber_id).format)\n"
133
 
                if DEBUG_MODE:
134
 
                        syslog('info', 'DlistUtils(get_format):Executing query:\n%s', command)
135
 
                return int(self.store.get(Subscriber,subscriber_id).format)     
136
 
 
137
 
        @decfunc
138
 
        def setDisable(self, member, flag):
139
 
                """Disable/enable delivery based on mm_cfg.DisableDelivery"""
140
 
                command = "result = self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,'utf-8'))\noldval = [(subscriber.suppress) for subscriber in result]\n"
141
 
                if DEBUG_MODE:
142
 
                        syslog('info', 'DlistUtils(setDisable):Executing query:\n%s\n Member whoes suppress value is to be found \n %s', command,member)
143
 
                result = self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,"utf-8"))
144
 
                oldval = [(subscriber.suppress) for subscriber in result]
145
 
                if DEBUG_MODE:
146
 
                        syslog('info','the value of oldval is %s:',oldval)
147
 
                oldval = oldval[0]              
148
 
                if flag:
149
 
                        newval = oldval | 1          # Disable delivery
150
 
                else:
151
 
                        newval = oldval & ~1          # Enable delivery
152
 
                if DEBUG_MODE:
153
 
                        syslog('info','the value of newval is %s:',newval)
154
 
                command = "self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,'utf-8')).set(suppress = newval)\n"
155
 
                if DEBUG_MODE:
156
 
                        syslog('info', 'DlistUtils(setDisable):Executing query:\n%s', command)
157
 
                self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,"utf-8")).set(suppress = newval)    
158
 
 
159
 
        @decfunc
160
 
        def setDigest(self, member, flag):
161
 
                """Disable/enable delivery based on user digest status"""
162
 
 
163
 
                command = "result = self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,'utf-8'))\noldval = [(subscriber.suppress) for subscriber in result]\n"
164
 
                if DEBUG_MODE:
165
 
                        syslog('info', 'DlistUtils(setDigest):Executing query:\n%s', command)
166
 
                result = self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,"utf-8"))
167
 
                oldval = [(subscriber.suppress) for subscriber in result]
168
 
                oldval = oldval[0]
169
 
                if DEBUG_MODE:
170
 
                        syslog('info','value of oldval %s:',oldval)
171
 
 
172
 
                if flag:
173
 
                        newval = oldval | 2          # Suppress delivery (in favor of digests)
174
 
                else:
175
 
                        newval = oldval & ~2          # Enable delivery (instead of digests)
176
 
 
177
 
                command = "self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,'utf-8')).set(suppress = newval)\n"
178
 
                if DEBUG_MODE:
179
 
                        syslog('info', 'DlistUtils(setDigest):Executing query:\n%s', command)
180
 
                self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,"utf-8")).set(suppress = newval)
181
 
 
182
 
        @decfunc
183
 
        def changePreference(self, member, preference):
184
 
                """Change a user's default preference for new threads."""
185
 
                command = "self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,'utf-8')).set(preference = preference)\n"
186
 
                if DEBUG_MODE:
187
 
                        syslog('info', 'DlistUtils(changePreference):Executing query:\n%s', command)    
188
 
                self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,"utf-8")).set(preference = preference)
189
 
 
190
 
        @decfunc
191
 
        def changeFormat(self, member, format):
192
 
                """Change a user's preferred delivery format (plain text and/or html)"""
193
 
                command = "self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,'utf-8')).set(format = format)\n"
194
 
                if DEBUG_MODE:
195
 
                        syslog('info', 'DlistUtils(changeFormat):Executing query:\n%s', command)
196
 
                self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,"utf-8")).set(format = format)      
197
 
 
198
 
        ### We have to watch out for a tricky case (that actually happened):
199
 
        ### User foo@bar.com changes her address to something already in the
200
 
        ### subscriber database (possibly deleted).
201
 
        @decfunc
202
 
        def changeAddress(self, oldaddr, newaddr):
203
 
                """Change email address in SQL database"""
204
 
                #if DEBUG_MODE:
205
 
                        #syslog('info', "Changing email address on %s from '%s' to '%s'", self.mlist.internal_name(), oldaddr, newaddr)
206
 
                ## Check if newaddr is in sql database          
207
 
                num_matches = self.store.find(Subscriber,Subscriber.mailman_key == unicode(newaddr,"utf-8")).count()
208
 
        
209
 
                if num_matches > 1:
210
 
                        #syslog('error', 'Multiple users with same key (%s): %s', self.mlist.internal_name(), newaddr)
211
 
                        return
212
 
                if num_matches == 1:
213
 
                        self.mergeSubscribers(oldaddr, newaddr)
214
 
                        
215
 
                command = "num_matches = self.store.find(Subscriber,Subscriber.mailman_key == unicode(newaddr,'utf-8')).count()\nself.store.find(Subscriber,Subscriber.mailman_key == unicode(oldaddr,'utf-8')).set(mailman_key = unicode(newaddr,'utf-8'))\nself.store.find(Subscriber,Subscriber.mailman_key == unicode(newaddr,'utf-8')).set(deleted = False)\n"
216
 
                if DEBUG_MODE:
217
 
                        syslog('info', 'DlistUtils(changeAddress):Executing query:\n%s', command)
218
 
                self.store.find(Subscriber,Subscriber.mailman_key == unicode(oldaddr,"utf-8")).set(mailman_key = unicode(newaddr,"utf-8")) 
219
 
                self.store.commit()                     
220
 
                self.store.find(Subscriber,Subscriber.mailman_key == unicode(newaddr,"utf-8")).set(deleted = False)
221
 
 
222
 
        @decfunc
223
 
        def unsubscribeFromList(self, key):
224
 
                """Indicate that a user has unsubscribed by setting the deleted flag in the subscriber table."""
225
 
 
226
 
                command = "self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,'utf-8')).set(deleted = True)\nself.store.find(Alias,Alias.subscriber_id == subscriber_id).remove()\n"
227
 
                if DEBUG_MODE:
228
 
                        syslog('info', 'DlisUtils(unsubscribeFromList):Executing query:\n%s', command)          
229
 
                self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,"utf-8")).set(deleted = True)
230
 
                # get the subscriber id from the mailman_key & delete all aliases  
231
 
                subscriber_id = self.getSubscriber_id_raw(key)
232
 
           
233
 
                if subscriber_id == None:
234
 
                        syslog('error', "DlistUtils.unsubscribeFromList called with '%s', but it can't be found in the SQL database", key)
235
 
                else:           
236
 
                        self.store.find(Alias,Alias.subscriber_id == subscriber_id).remove()
237
 
 
238
 
        @decfunc
239
 
        def subscribeToList(self, key):
240
 
                """Add a member to the subscriber database, or change the record from deleted if it was already present."""
241
 
        
242
 
                command = "count = (self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,'utf-8'))).count()\n"
243
 
                if DEBUG_MODE:
244
 
                        syslog('info', 'DlistUtils(subscribeToList):Executing query:\n%s', command)
245
 
                # First see if member is subscribed with deleted field = false
246
 
                count = (self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,"utf-8"))).count()
247
 
        
248
 
                if count:
249
 
                        command = "self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,'utf-8')).set(deleted = False)"
250
 
                        if DEBUG_MODE:
251
 
                                syslog('info', 'DlistUtils(subscribeToList):Executing query:\n%s', command)
252
 
                        self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,"utf-8")).set(deleted = False)
253
 
                else:
254
 
                        # format is text-only (1) by default
255
 
                        command = "subscriber = self.store.add(Subscriber())\nsubscriber.mailman_key = unicode(key,'utf-8')\nsubscriber.preference = 1\nsubscriber.deleted = False\nsubscriber.format = 1\nsubscriber.suppress = 0"
256
 
                        if DEBUG_MODE:
257
 
                                syslog('info', 'DlistUtils(subscribeToList)Executing query:\n%s', command)
258
 
                        self.mailman_key = unicode(key,"utf-8")
259
 
                        self.preference = 1
260
 
                        self.deleted = False
261
 
                        self.format = 1
262
 
                        self.suppress = 0
263
 
                        self.store.add(self)               
264
 
 
265
 
        @decfunc
266
 
        def mergeSubscribers(self, oldaddr, newaddr):
267
 
                # use the original subscriber id as the ID going forward
268
 
                if DEBUG_MODE:
269
 
                        syslog('info', 'Executing commands of mergeSubscribers:')
270
 
                result = self.store.find(Subscriber,Subscriber.mailman_key == unicode(oldaddr,"utf-8"))
271
 
                new_id = [(subscriber.subscriber_id) for subscriber in result]
272
 
                new_id = new_id[0]
273
 
                if DEBUG_MODE:
274
 
                        syslog('info', 'the value of new_id: %s', new_id)
275
 
                result = self.store.find(Subscriber,Subscriber.mailman_key == unicode(newaddr,"utf-8"))
276
 
                obsolete_id = [(subscriber.subscriber_id) for subscriber in result]
277
 
                obsolete_id = obsolete_id[0]    
278
 
                if DEBUG_MODE:
279
 
                        syslog('info', 'the value of obsolete_id: %s', obsolete_id)
280
 
 
281
 
                self.store.find(Message,Message.sender_id == obsolete_id).set(sender_id = new_id)
282
 
                self.store.find(Override,Override.subscriber_id == obsolete_id).set(subscriber_id = new_id)
283
 
                self.store.find(Alias,Alias.subscriber_id == obsolete_id).set(subscriber_id = new_id)
284
 
                self.store.find(Subscriber,Subscriber.subscriber_id == obsolete_id).remove()    
 
62
    __storm_table__ = "subscriber"
 
63
    subscriber_id = Int(primary = True,default = AutoReload)
 
64
    mailman_key = Unicode()
 
65
    preference = Int(default = 1)
 
66
    format = Int(default = 3)
 
67
    deleted = Bool(default = False)
 
68
    suppress = Int(default = 0)
 
69
 
 
70
    def __init__(self,mlist):
 
71
        self.mlist = mlist
 
72
        self.database = getConn(mlist)
 
73
 
 
74
    @decfunc
 
75
    def getSubscriber_id_raw(self, addr):
 
76
        if addr == None:
 
77
            return None
 
78
 
 
79
        command = "result = self.store.find(Subscriber,Subscriber.mailman_key == unicode(addr.lower(),'utf-8'))\na = [(subscriber.subscriber_id) for subscriber in result]\n"
 
80
        if DEBUG_MODE:
 
81
            syslog('info', "DlistUtils:(getSubscriber_id_raw)executing query:\n%s", command)
 
82
        #storm recognizes unicode only
 
83
        result = self.store.find(Subscriber,Subscriber.mailman_key == unicode(addr.lower(),"utf-8")) 
 
84
        a = [(subscriber.subscriber_id) for subscriber in result]
 
85
        if DEBUG_MODE:
 
86
            syslog('info', 'value of a is: %s\n', a)
 
87
 
 
88
        #The ResultSet "a" obtained in above storm command and later on in similar ones is always a list
 
89
        if a == []:
 
90
            return None 
 
91
        else:
 
92
            return a[0]
 
93
 
 
94
    def getSubscriber_id_raw_or_die(self, addr):
 
95
        result = self.getSubscriber_id_raw(addr)
 
96
        if (result == None):
 
97
            #syslog('error', 'getSubscriber_id_raw_or_die /msg nickserv register <your-password> <your-email>unable to find address /%s/ for mailing list /%s/', addr, self.mlist.internal_name())
 
98
            raise ErrorsDlist.InternalError
 
99
        else:
 
100
            return result
 
101
 
 
102
    def getSubscriber_id(self, msg, msgdata, safe=0, loose=0):
 
103
        """Returns the subscriber_id of the sender of a message and sets the 'subscriber_id' field in the msg object."""
 
104
        fromAddr = email.Utils.parseaddr(msg['From'])[1]
 
105
        try:
 
106
            subscriber_id = msgdata['subscriber_id']
 
107
            return subscriber_id
 
108
        except:
 
109
            subscriber_id = self.getSubscriber_id_raw(fromAddr)
 
110
            if subscriber_id == None:
 
111
                alias = Alias(self.mlist)
 
112
                bestAddr = alias.canonicalize_sender(msg.get_senders())
 
113
                subscriber_id = self.getSubscriber_id_raw(bestAddr)
 
114
                if subscriber_id == None:
 
115
                    #if DEBUG_MODE:
 
116
                        #syslog('info', "DlistUtils.getSubscriber_id: subscriber_id is None and safe is %d", safe)
 
117
                    if safe:
 
118
                        # This could happen if a non-member is given permission to post
 
119
                        return 0
 
120
                    else:
 
121
                        #if DEBUG_MODE:
 
122
                            #syslog('info', "Raising ErrorsDlist.NotMemberError")
 
123
                        raise ErrorsDlist.NotMemberError("Your request could not be processed because %s is not subscribed to %s.  Perhaps you are subscribed with a different email address, which forwards to %s.  If so, please log into %s and add this as an 'other incoming email address'." % (fromAddr, self.mlist.internal_name(), fromAddr, self.mlist.GetScriptURL('options', 1))) 
 
124
            if DEBUG_MODE:
 
125
                syslog('info', 'subscriber_id = %d\n', subscriber_id)
 
126
            msgdata['subscriber_id'] = subscriber_id
 
127
            return subscriber_id
 
128
 
 
129
    @decfunc
 
130
    def get_format(self, subscriber_id):
 
131
        command = "return int(self.store.get(Subscriber,subscriber_id).format)\n"
 
132
        if DEBUG_MODE:
 
133
            syslog('info', 'DlistUtils(get_format):Executing query:\n%s', command)
 
134
        return int(self.store.get(Subscriber,subscriber_id).format)    
 
135
 
 
136
    @decfunc
 
137
    def setDisable(self, member, flag):
 
138
        """Disable/enable delivery based on mm_cfg.DisableDelivery"""
 
139
        command = "result = self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,'utf-8'))\noldval = [(subscriber.suppress) for subscriber in result]\n"
 
140
        if DEBUG_MODE:
 
141
            syslog('info', 'DlistUtils(setDisable):Executing query:\n%s\n Member whoes suppress value is to be found \n %s', command,member)
 
142
        result = self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,"utf-8"))
 
143
        oldval = [(subscriber.suppress) for subscriber in result]
 
144
        if oldval == []:
 
145
            if DEBUG_MODE:
 
146
                syslog('info','oldval is an empty list.\nThis can happen either because of\n 1)Permission issues (Do a: bin/check_perms)\n 2)Inconsistency between database and pickle files (A user is in the database but not in pickle files or vice versa,Do a bin/find_problems.py)')
 
147
        oldval = oldval[0]
 
148
        if DEBUG_MODE:
 
149
            syslog('info','the value of oldval is %s:',oldval)
 
150
 
 
151
        if flag:
 
152
            newval = oldval | 1          # Disable delivery
 
153
        else:
 
154
            newval = oldval & ~1          # Enable delivery
 
155
        if DEBUG_MODE:
 
156
            syslog('info','the value of newval is %s:',newval)
 
157
        command = "self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,'utf-8')).set(suppress = newval)\n"
 
158
        if DEBUG_MODE:
 
159
            syslog('info', 'DlistUtils(setDisable):Executing query:\n%s', command)
 
160
        self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,"utf-8")).set(suppress = newval)       
 
161
 
 
162
    @decfunc
 
163
    def setDigest(self, member, flag):
 
164
        """Disable/enable delivery based on user digest status"""
 
165
 
 
166
        command = "result = self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,'utf-8'))\noldval = [(subscriber.suppress) for subscriber in result]\n"
 
167
        if DEBUG_MODE:
 
168
            syslog('info', 'DlistUtils(setDigest):Executing query:\n%s', command)
 
169
        result = self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,"utf-8"))
 
170
        oldval = [(subscriber.suppress) for subscriber in result]
 
171
        if oldval == []:
 
172
            if DEBUG_MODE:
 
173
                syslog('info','oldval is an empty list.\nThis can happen either because of\n 1)permission issues (Do a: bin/check_perms)\n 2)Inconsistency between database and pickle files (A user is in the database but not in pickle files or vice versa,Do a bin/find_problems.py)')
 
174
        oldval = oldval[0]
 
175
        if DEBUG_MODE:
 
176
            syslog('info','value of oldval %s:',oldval)
 
177
 
 
178
        if flag:
 
179
            newval = oldval | 2          # Suppress delivery (in favor of digests)
 
180
        else:
 
181
            newval = oldval & ~2          # Enable delivery (instead of digests)
 
182
 
 
183
        command = "self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,'utf-8')).set(suppress = newval)\n"
 
184
        if DEBUG_MODE:
 
185
            syslog('info', 'DlistUtils(setDigest):Executing query:\n%s', command)
 
186
        self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,"utf-8")).set(suppress = newval)
 
187
 
 
188
    @decfunc
 
189
    def changePreference(self, member, preference):
 
190
        """Change a user's default preference for new threads."""
 
191
        command = "self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,'utf-8')).set(preference = preference)\n"
 
192
        if DEBUG_MODE:
 
193
            syslog('info', 'DlistUtils(changePreference):Executing query:\n%s', command)    
 
194
        self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,"utf-8")).set(preference = preference)
 
195
 
 
196
    @decfunc
 
197
    def changeFormat(self, member, format):
 
198
        """Change a user's preferred delivery format (plain text and/or html)"""
 
199
        command = "self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,'utf-8')).set(format = format)\n"
 
200
        if DEBUG_MODE:
 
201
            syslog('info', 'DlistUtils(changeFormat):Executing query:\n%s', command)
 
202
        self.store.find(Subscriber,Subscriber.mailman_key == unicode(member,"utf-8")).set(format = format)    
 
203
 
 
204
    ### We have to watch out for a tricky case (that actually happened):
 
205
    ### User foo@bar.com changes her address to something already in the
 
206
    ### subscriber database (possibly deleted).
 
207
    @decfunc
 
208
    def changeAddress(self, oldaddr, newaddr):
 
209
        """Change email address in SQL database"""
 
210
        #if DEBUG_MODE:
 
211
        #syslog('info', "Changing email address on %s from '%s' to '%s'", self.mlist.internal_name(), oldaddr, newaddr)
 
212
        ## Check if newaddr is in sql database        
 
213
        num_matches = self.store.find(Subscriber,Subscriber.mailman_key == unicode(newaddr,"utf-8")).count()
 
214
 
 
215
        if num_matches > 1:
 
216
            #syslog('error', 'Multiple users with same key (%s): %s', self.mlist.internal_name(), newaddr)
 
217
            return
 
218
        if num_matches == 1:
 
219
            self.mergeSubscribers(oldaddr, newaddr)
 
220
 
 
221
        command = "num_matches = self.store.find(Subscriber,Subscriber.mailman_key == unicode(newaddr,'utf-8')).count()\nself.store.find(Subscriber,Subscriber.mailman_key == unicode(oldaddr,'utf-8')).set(mailman_key = unicode(newaddr,'utf-8'))\nself.store.find(Subscriber,Subscriber.mailman_key == unicode(newaddr,'utf-8')).set(deleted = False)\n"
 
222
        if DEBUG_MODE:
 
223
            syslog('info', 'DlistUtils(changeAddress):Executing query:\n%s', command)
 
224
        self.store.find(Subscriber,Subscriber.mailman_key == unicode(oldaddr,"utf-8")).set(mailman_key = unicode(newaddr,"utf-8")) 
 
225
        self.store.commit()            
 
226
        self.store.find(Subscriber,Subscriber.mailman_key == unicode(newaddr,"utf-8")).set(deleted = False)
 
227
 
 
228
    @decfunc
 
229
    def unsubscribeFromList(self, key):
 
230
        """Indicate that a user has unsubscribed by setting the deleted flag in the subscriber table."""
 
231
 
 
232
        command = "self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,'utf-8')).set(deleted = True)\nself.store.find(Alias,Alias.subscriber_id == subscriber_id).remove()\n"
 
233
        if DEBUG_MODE:
 
234
            syslog('info', 'DlisUtils(unsubscribeFromList):Executing query:\n%s', command)          
 
235
        self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,"utf-8")).set(deleted = True)
 
236
        # get the subscriber id from the mailman_key & delete all aliases  
 
237
        subscriber_id = self.getSubscriber_id_raw(key)
 
238
 
 
239
        if subscriber_id == None:
 
240
            syslog('error', "DlistUtils.unsubscribeFromList called with '%s', but it can't be found in the SQL database", key)
 
241
        else:            
 
242
            self.store.find(Alias,Alias.subscriber_id == subscriber_id).remove()
 
243
 
 
244
    @decfunc
 
245
    def subscribeToList(self, key):
 
246
        """Add a member to the subscriber database, or change the record from deleted if it was already present."""
 
247
 
 
248
        command = "count = (self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,'utf-8'))).count()\n"
 
249
        if DEBUG_MODE:
 
250
            syslog('info', 'DlistUtils(subscribeToList):Executing query:\n%s', command)
 
251
        # First see if member is subscribed with deleted field = false
 
252
        count = (self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,"utf-8"))).count()
 
253
 
 
254
        if count:
 
255
            command = "self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,'utf-8')).set(deleted = False)"
 
256
            if DEBUG_MODE:
 
257
                syslog('info', 'DlistUtils(subscribeToList):Executing query:\n%s', command)
 
258
            self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,"utf-8")).set(deleted = False)
 
259
        else:
 
260
            # format is text-only (1) by default
 
261
            command = "subscriber = self.store.add(Subscriber())\nsubscriber.mailman_key = unicode(key,'utf-8')\nsubscriber.preference = 1\nsubscriber.deleted = False\nsubscriber.format = 1\nsubscriber.suppress = 0"
 
262
            if DEBUG_MODE:
 
263
                syslog('info', 'DlistUtils(subscribeToList)Executing query:\n%s', command)
 
264
            self.mailman_key = unicode(key,"utf-8")
 
265
            self.preference = 1
 
266
            self.deleted = False
 
267
            self.format = 1
 
268
            self.suppress = 0
 
269
            self.store.add(self)
 
270
 
 
271
    @decfunc
 
272
    def mergeSubscribers(self, oldaddr, newaddr):
 
273
        # use the original subscriber id as the ID going forward
 
274
        if DEBUG_MODE:
 
275
            syslog('info', 'Executing commands of mergeSubscribers:')
 
276
        result = self.store.find(Subscriber,Subscriber.mailman_key == unicode(oldaddr,"utf-8"))
 
277
        new_id = [(subscriber.subscriber_id) for subscriber in result]
 
278
        new_id = new_id[0]
 
279
        if DEBUG_MODE:
 
280
            syslog('info', 'the value of new_id: %s', new_id)
 
281
        result = self.store.find(Subscriber,Subscriber.mailman_key == unicode(newaddr,"utf-8"))
 
282
        obsolete_id = [(subscriber.subscriber_id) for subscriber in result]
 
283
        obsolete_id = obsolete_id[0]    
 
284
        if DEBUG_MODE:
 
285
            syslog('info', 'the value of obsolete_id: %s', obsolete_id)
 
286
 
 
287
        self.store.find(Message,Message.sender_id == obsolete_id).set(sender_id = new_id)
 
288
        self.store.find(Override,Override.subscriber_id == obsolete_id).set(subscriber_id = new_id)
 
289
        self.store.find(Alias,Alias.subscriber_id == obsolete_id).set(subscriber_id = new_id)
 
290
        self.store.find(Subscriber,Subscriber.subscriber_id == obsolete_id).remove()    
285
291
 
286
292
class Message(object):
287
 
        __storm_table__ = "message"
288
 
        message_id = Int(primary = True,default = AutoReload)
289
 
        sender_id = Int()
290
 
        subscriber = Reference(sender_id,Subscriber.subscriber_id)
291
 
        subject = Unicode()
292
 
        thread_id = Int()
293
 
 
294
 
        def __init__(self,mlist):
295
 
                self.mlist = mlist
296
 
                self.database = getConn(mlist)
297
 
 
298
 
        @decfunc
299
 
        def createMessage(self, msg, msgdata):
300
 
                """Create a new message (in the database), returning its id."""
301
 
                subscriber = Subscriber(self.mlist)
302
 
                senderID = subscriber.getSubscriber_id(msg, msgdata, safe=1, loose=1)            
303
 
                # extract subject and escape quotes
304
 
                subject = msg['Subject'].encode().replace("'", "''")
305
 
                
306
 
                try:
307
 
                        threadID = msgdata['thread_id']
308
 
                except:
309
 
                        threadID = -1
310
 
        
311
 
                command = "message = self.store.add(Message())\nmessage.sender_id = senderID\nmessage.thread_id = threadID\nmessage.subject = unicode(subject,'utf-8')\nmessageID = self.store.find(Message).max(Message.message_id)\n"
312
 
                if DEBUG_MODE:
313
 
                        syslog('info','DlistUtils:(createMessage)executing query:\n%s',command)
314
 
                #message_id has autoreload set,its value will be serially updated in database 
315
 
                self.sender_id = senderID
316
 
                self.thread_id = threadID
317
 
                self.subject = unicode(subject,"utf-8") 
318
 
                self.store.add(self) 
319
 
                messageID = self.store.find(Message).max(Message.message_id)    
320
 
                if DEBUG_MODE:
321
 
                        syslog('info','Result of query(messageID) is: %s\n',messageID)
322
 
 
323
 
                return messageID
 
293
    __storm_table__ = "message"
 
294
    message_id = Int(primary = True,default = AutoReload)
 
295
    sender_id = Int()
 
296
    subscriber = Reference(sender_id,Subscriber.subscriber_id)
 
297
    subject = Unicode()
 
298
    thread_id = Int()
 
299
 
 
300
    def __init__(self,mlist):
 
301
        self.mlist = mlist
 
302
        self.database = getConn(mlist)
 
303
 
 
304
    @decfunc
 
305
    def createMessage(self, msg, msgdata):
 
306
        """Create a new message (in the database), returning its id."""
 
307
        subscriber = Subscriber(self.mlist)
 
308
        senderID = subscriber.getSubscriber_id(msg, msgdata, safe=1, loose=1)
 
309
        # extract subject and escape quotes
 
310
        subject = msg['Subject'].encode().replace("'", "''")
 
311
        try:
 
312
            threadID = msgdata['thread_id']
 
313
        except:
 
314
            threadID = -1
 
315
        command = "message = self.store.add(Message())\nmessage.sender_id = senderID\nmessage.thread_id = threadID\nmessage.subject = unicode(subject,'utf-8')\nmessageID = self.store.find(Message).max(Message.message_id)\n"
 
316
        if DEBUG_MODE:
 
317
            syslog('info','DlistUtils:(createMessage)executing query:\n%s',command)
 
318
        #message_id has autoreload set,its value will be serially updated in database 
 
319
        self.sender_id = senderID
 
320
        self.thread_id = threadID
 
321
        self.subject = unicode(subject,"utf-8") 
 
322
        self.store.add(self) 
 
323
        messageID = self.store.find(Message).max(Message.message_id)     
 
324
        if DEBUG_MODE:
 
325
            syslog('info','Result of query(messageID) is: %s\n',messageID)
 
326
        return messageID
324
327
 
325
328
class Thread(object):
326
 
        __storm_table__ = "thread"
327
 
        thread_id = Int(primary = True,default = AutoReload)
328
 
        thread_name = Unicode()
329
 
        base_message_id = Int()
330
 
        message = Reference(base_message_id,Message.message_id)
331
 
        status = Int(default = 0)
332
 
        parent = Int()
333
 
 
334
 
        def __init__(self,mlist):
335
 
                self.mlist = mlist
336
 
                self.database = getConn(mlist)
337
 
 
338
 
        @decfunc
339
 
        def createThread(self, msg, msgdata, threadBase):
340
 
                """Create a new thread, returning its unique id, name."""
341
 
                message = Message(self.mlist)
342
 
                subscriber = Subscriber(self.mlist) 
343
 
                msgdata['message_id'] = message.createMessage(msg, msgdata)
344
 
                senderID = subscriber.getSubscriber_id(msg, msgdata, safe=1, loose=1)
345
 
        
346
 
                command = "thread = self.store.add(Thread())\nthread.base_message_id = msgdata['message_id']\nthreadID = self.store.find(Thread).max(Thread.thread_id)\nself.store.find(Message,Message.message_id == msgdata['message_id']).set(thread_id = threadID)\n"
347
 
                if DEBUG_MODE:
348
 
                        syslog('info','DlistUtils:(createThread)executing query:\n%s',command)
349
 
                self.base_message_id = msgdata['message_id']
350
 
                self.store.add(self)
351
 
                threadID = self.store.find(Thread).max(Thread.thread_id)        
352
 
                self.store.find(Message,Message.message_id == msgdata['message_id']).set(thread_id = threadID)
353
 
 
354
 
                ## Choose a unique name for the thread
355
 
                # Try to get the name from the to-line, e.g., listname+new+name@hostname
356
 
                if threadBase:
357
 
                        threadBase = self.alphanumericOnly(threadBase).lower()
358
 
                        if threadBase in lousyThreadNames or not len(threadBase):
359
 
                                threadBase = None
360
 
 
361
 
                # If a name wasn't explicitly specified, try to get one from the subject
362
 
                if not threadBase:
363
 
                        # No thread name was specified -- try to get from subject line.
364
 
                        # If none in subject line, the threadID will be returned
365
 
                        threadBase = self.subjectToName(msg['Subject'].encode(), threadID)      
366
 
 
367
 
                # Make sure the thread can fit in the field (char(16))
368
 
                threadBase = threadBase[:13]
369
 
                command = "num = self.store.find(Thread,Thread.thread_name == unicode(threadBase,'utf-8')).count()\nself.store.find(Thread,Thread.thread_id == threadID).set(thread_name = threadName)\n"
370
 
                if DEBUG_MODE:
371
 
                        syslog('info','DlistUtils:(createThread)executing query:\n%s',command)
372
 
                # If the threadBase is not unique, make threadBase unique by appending a number
373
 
                num = self.store.find(Thread,Thread.thread_name == unicode(threadBase,"utf-8")).count() 
374
 
        
375
 
                if not num:
376
 
                        threadName = unicode(threadBase,"utf-8")
377
 
                else:
378
 
                        threadName = unicode(threadBase + str(num+1),"utf-8" )
379
 
 
380
 
                self.store.find(Thread,Thread.thread_id == threadID).set(thread_name = threadName)
381
 
        
382
 
                return (threadID, threadName.encode("utf-8"))
383
 
 
384
 
        def email_recepients(self, msg, msgdata, lists, pref):
385
 
         
386
 
                """Finding the email addresses of matching subscribers from lists,and using the list info to email everyone"""
387
 
                returnList = GetEmailAddress(self.mlist, lists) 
388
 
                msgdata['recips'] = returnList
389
 
                self.setFooterText(msg, msgdata, pref)
390
 
                dq = get_switchboard(mm_cfg.DLISTQUEUE_DIR)
391
 
                dq.enqueue(msg, msgdata, listname=self.mlist.internal_name())
392
 
 
393
 
        @decfunc
394
 
        def newThread(self, msg, msgdata, threadBase=None):
395
 
                """Starts a new thread, including creating and enqueueing the initial messages"""
396
 
                id, name = self.createThread(msg, msgdata, threadBase)
397
 
                msgdata['thread_id'] = id
398
 
                msgdata['thread_name'] = name
399
 
 
400
 
                # Delete any other 'To' headings
401
 
                del msg['To']
402
 
                msg['To'] =  '%s+%s@%s' % (self.mlist.internal_name(),
403
 
                                           name,
404
 
                                           self.mlist.host_name)        
405
 
                for i in (1,2):
406
 
                        # different emails for different prefs, so we need to queue separately
407
 
                        if(i==1):
408
 
                                #For condition where preference = True
409
 
                                pref=True
410
 
                                command = "lists = [(subscriber.mailman_key.encode('utf-8')) for subscriber in result_new_sql]\n"
411
 
                                if DEBUG_MODE:
412
 
                                        syslog('info', 'DlistUtils:(newThread)executing query:\n%sfor pref = true\n', command)
413
 
                        if(i==2):
414
 
                                #For condition where preference = False
415
 
                                pref=False
416
 
                                command = "lists = [(subscriber.mailman_key.encode('utf-8')) for subscriber in result_new_sql]\n"
417
 
                                if DEBUG_MODE:
418
 
                                        syslog('info', 'DlistUtils:(newThread)executing query:\n%sfor pref = false\n', command)
419
 
 
420
 
                        #Execute a SELECT statement, to find the list of matching subscribers.
421
 
                        result_new_sql = self.store.find(Subscriber,And(Subscriber.preference == pref,Subscriber.deleted == False,Subscriber.suppress == 0))
422
 
                        lists = [(subscriber.mailman_key.encode('utf-8')) for subscriber in result_new_sql]
423
 
                        if DEBUG_MODE:
424
 
                                syslog('info', 'value of lists: %s\n', lists)
425
 
                        self.email_recepients(msg, msgdata, lists, pref)
426
 
 
427
 
                # Make original message go to nobody (but be archived)
428
 
                msgdata['recips'] = []
429
 
 
430
 
        @decfunc
431
 
        def continueThread(self, msg, msgdata, threadReference):
432
 
                """Continue an existing thread, no return value."""
433
 
                subscriber = Subscriber(self.mlist)
434
 
                message = Message(self.mlist)
435
 
                senderID = subscriber.getSubscriber_id(msg, msgdata, safe=1, loose=1)
436
 
                msgdata['message_id'] = message.createMessage(msg, msgdata)
437
 
 
438
 
                pref=True
439
 
 
440
 
                # email selected people
441
 
                #Execute a SELECT statement, to find the list of matching subscribers.
442
 
                command = "lists = [(subscriber.mailman_key.encode('utf-8')) for subscriber in result_continue_sql]\n"
443
 
                if DEBUG_MODE:
444
 
                        syslog('info', 'DlistUtils:(continueThread) executing query:\n%s', command)
445
 
        
446
 
                result_pref_one = self.store.find((Subscriber,Override),And(Override.subscriber_id == Subscriber.subscriber_id,Override.thread_id == msgdata['thread_id'],Override.preference == 0))
447
 
                lists_pref_one = [(override.subscriber_id) for (subscriber,override) in result_pref_one]
448
 
 
449
 
                result_pref_zero = self.store.find((Subscriber,Override),And(Override.subscriber_id == Subscriber.subscriber_id,Override.thread_id == msgdata['thread_id'],Override.preference == 1))
450
 
                lists_pref_zero = [(override.subscriber_id) for (subscriber,override) in result_pref_zero]
451
 
 
452
 
                result_continue_sql = self.store.find(Subscriber,And(Subscriber.deleted == False,Subscriber.suppress == 0,Or(And(Subscriber.preference == 1,lists_pref_one == []),And(Subscriber.preference == 0,lists_pref_zero != []))))
453
 
                lists = [(subscriber.mailman_key.encode('utf-8')) for subscriber in result_continue_sql]
454
 
        
455
 
                self.email_recepients(msg, msgdata, lists, pref)
456
 
 
457
 
                # Make original message go to nobody (but be archived)
458
 
                msgdata['recips'] = []
459
 
        
460
 
        @decfunc
461
 
        def threadIDandName(self, threadReference):
462
 
                """Given thread_id or thread_name, determine the other, returning (thread_id, thread_name)"""    
463
 
                try:
464
 
                        thread_id = int(thread_reference)
465
 
                        try:
466
 
                                command = "result = self.store.find(Thread,Thread.thread_id == thread_id)\nthread_name = [(thread.thread_name) for thread in result]\nthread_name = thread_name.encode('utf-8')\n"
467
 
                                if DEBUG_MODE:
468
 
                                        syslog('info', 'DlistUtils(threadIDandName)Executing query:\n%s', command)
469
 
                                result = self.store.find(Thread,Thread.thread_id == thread_id)
470
 
                                thread_name = [(thread.thread_name) for thread in result]
471
 
                                thread_name = thread_name.encode('utf-8')            
472
 
                        except:
473
 
                                raise NonexistentThreadRequest("Your message could not be sent because you specified a nonexistent conversation (%d).  Perhaps you meant to start a new conversation, which you can do by addressing your message to %s+new@%s" % (thread_id, self.mlist.real_name, self.mlist.host_name))
474
 
                except:
475
 
                        thread_name = threadReference.lower()
476
 
                        if DEBUG_MODE:
477
 
                                syslog('info', 'thread_name = %s\n', thread_name)
478
 
                        command = "result = self.store.find(Thread,Thread.thread_name == unicode(thread_name,'utf-8'))\nthread_id = [(thread.thread_id) for thread in result]\n"
479
 
                        if DEBUG_MODE:
480
 
                                syslog('info', 'DlistUtils(threadIDandName)Executing query:\n%s', command)
481
 
                        result = self.store.find(Thread,Thread.thread_name == unicode(thread_name,'utf-8'))
482
 
                        thread_id = [(thread.thread_id) for thread in result]
483
 
                        thread_id = thread_id[0]#to convert a list to int
484
 
                        if DEBUG_MODE:
485
 
                                syslog('info', 'thread_id: %s\n', thread_id)            
486
 
 
487
 
                        if thread_id == None:
488
 
                                raise NonexistentThreadRequest("Your message could not be sent because you addressed it to a nonexistent conversation (%s).  Perhaps you meant to start a new conversation named %s, which you can do by addressing your message to %s+new+%s@%s" % (thread_name, thread_name, self.mlist.real_name, thread_name, self.mlist.host_name))
489
 
 
490
 
                return (thread_id, thread_name)
491
 
 
492
 
        def getThreadAddress(msgdata):
493
 
                """The list address for any given thread."""
494
 
                return "%s+%s@%s" % (self.mlist.internal_name(), msgdata['thread_name'], self.mlist.host_name)
495
 
 
496
 
        def subscribeToThread(self, msg, msgdata):
497
 
                """Subscribe the sender of a message to a given thread."""
498
 
                override = Override(self.mlist)
499
 
                override.override(msg, msgdata, 1)
500
 
 
501
 
        def unsubscribeFromThread(self, msg, msgdata):
502
 
                """Unubscribe the sender of a message from a given thread."""
503
 
                override = Override(self.mlist)
504
 
                override.override(msg, msgdata, 0)
505
 
 
506
 
        def alphanumericOnly(self,s):
507
 
                """Filter any non-letter characters from a string"""
508
 
                result = [letter for letter in s if letter in string.ascii_letters or letter in string.digits]
509
 
                return string.join(result, '')
510
 
        
511
 
        def subjectToName(self,subject, threadID):
512
 
                """Return a lower-case name for a new thread based on the subject, if present, or on the threadID"""
513
 
                result = None
514
 
    
515
 
                if subject == '':
516
 
                        return str(threadID)
517
 
 
518
 
                subjectWords = [self.alphanumericOnly(w) for w in subject.split()]
519
 
    
520
 
                # Choose the longest word of 4 or more characters
521
 
                maxLength = 3
522
 
                maxWord = None
523
 
                for word in subjectWords and word not in lousyThreadNames:#TODO and word not in....was not there before
524
 
                        if len(word) > maxLength:
525
 
                                maxWord = word
526
 
                                maxLength = len(word)
527
 
                if maxWord:
528
 
                        result = maxWord.lower()
529
 
        
530
 
                if not result:
531
 
                        # Choose the first word that's not a stop word or lousy thread name
532
 
                        stopWords = ["a", "an", "the", "of", "re", "you", "i", "no", "not", "do", "for"]
533
 
                        for word in subjectWords:
534
 
                                if word not in stopWords and word not in lousyThreadNames:
535
 
                                        result = word.lower()
536
 
                                        break
537
 
 
538
 
                if not result:
539
 
                        # If no other candidate, just return the first subject word
540
 
                        result = subjectWords[0].lower() + str(threadID)#TODO str(threadID) was not before
541
 
        
542
 
                return result[:12]
543
 
 
544
 
        def setFooterText(self, msg, msgdata, preference):
545
 
                msgdata['dlists_preference'] = preference
546
 
                thread_name = msgdata['thread_name']
547
 
                thread_id = msgdata['thread_id']
548
 
                override = Override(self.mlist)
549
 
                web_addr = override.overrideURL(self.mlist.internal_name(), self.mlist.host_name, thread_id, not preference)
550
 
                if preference == 1:
551
 
                        subscribe_string = "unsubscribe"
552
 
                        preposition = "from"
553
 
                else:
554
 
                        subscribe_string = "subscribe"
555
 
                        preposition = "to"
556
 
                email_addr = '%s+%s+%s@%s' % (self.mlist.internal_name(), thread_name, subscribe_string, self.mlist.host_name)
557
 
                #if DEBUG_MODE:
558
 
                        #syslog('info', "msg['Subject'] = /%s/", msg['Subject'])
559
 
                subject = urllib.quote(msg['Subject'].encode())
560
 
                post_addr = "%s+%s@%s" % (self.mlist.internal_name(), thread_name, self.mlist.host_name)
561
 
                post_addr_with_subject = "%s?Subject=%s" % (post_addr, subject)
562
 
                #if DEBUG_MODE:
563
 
                        #syslog('info', 'post_addr_with_subject = %s', post_addr)
564
 
 
565
 
                # Used in Handlers/ToDigest.py
566
 
                msgdata['contribute'] = "To contribute to this conversation, send " \
567
 
                                        "your message to <%s+%s@%s>\n" % \
568
 
                                        (self.mlist.internal_name(), msgdata['thread_name'], self.mlist.host_name)
569
 
 
570
 
                # Used in ToArchive
571
 
                msgdata['contribute-html'] = 'To contribute to this conversation, send your message to <a href="mailto:%s">%s</a>\n' % (post_addr, post_addr)
572
 
                msgdata['footer-text'] = '\n\nTo %s %s this conversation, send email to <%s> or visit <%s>\nTo contribute to this conversation, use your mailer\'s reply-all or reply-group command or send your message to %s\nTo start a new conversation, send email to <%s+new@%s>\nTo unsubscribe entirely from %s, send email to <%s-request@%s> with subject unsubscribe.' % (subscribe_string, preposition, email_addr, web_addr, post_addr, self.mlist.internal_name(), self.mlist.host_name, self.mlist.internal_name(), self.mlist.internal_name(), self.mlist.host_name)
573
 
                #if DEBUG_MODE:
574
 
                        #syslog('info', 'footer-text = /%s/', msgdata['footer-text'])
575
 
                msgdata['footer-html'] = '<br>To %s %s this conversation, send email to <a href="mailto:%s">%s</a> or visit <a href="%s">%s</a>.<br>To contribute to this conversation, use your mailer\'s reply-all or reply-group command or send your message to <a href="mailto:%s?subject=%s">%s</a>.<br>To start a new conversation, send email to <a href="mailto:%s+new@%s">%s+new@%s</a><br>To unsubscribe entirely from %s, send mail to <a href="mailto:%s-request@%s?subject=unsubscribe">%s-request@%s</a> with subject unsubscribe.' % (subscribe_string, preposition, email_addr, email_addr, web_addr, web_addr, post_addr, subject, post_addr, self.mlist.internal_name(), self.mlist.host_name, self.mlist.internal_name(), self.mlist.host_name, self.mlist.internal_name(), self.mlist.internal_name(), self.mlist.host_name, self.mlist.internal_name(), self.mlist.host_name)
 
329
    __storm_table__ = "thread"
 
330
    thread_id = Int(primary = True,default = AutoReload)
 
331
    thread_name = Unicode()
 
332
    base_message_id = Int()
 
333
    message = Reference(base_message_id,Message.message_id)
 
334
    status = Int(default = 0)
 
335
    parent = Int()
 
336
 
 
337
    def __init__(self,mlist):
 
338
        self.mlist = mlist
 
339
        self.database = getConn(mlist)
 
340
 
 
341
    @decfunc
 
342
    def createThread(self, msg, msgdata, threadBase):
 
343
        """Create a new thread, returning its unique id, name."""
 
344
        message = Message(self.mlist)
 
345
        subscriber = Subscriber(self.mlist) 
 
346
        msgdata['message_id'] = message.createMessage(msg, msgdata)
 
347
        senderID = subscriber.getSubscriber_id(msg, msgdata, safe=1, loose=1)
 
348
        command = "thread = self.store.add(Thread())\nthread.base_message_id = msgdata['message_id']\nthreadID = self.store.find(Thread).max(Thread.thread_id)\nself.store.find(Message,Message.message_id == msgdata['message_id']).set(thread_id = threadID)\n"
 
349
        if DEBUG_MODE:
 
350
            syslog('info','DlistUtils:(createThread)executing query:\n%s',command)
 
351
        self.base_message_id = msgdata['message_id']
 
352
        self.store.add(self)
 
353
        threadID = self.store.find(Thread).max(Thread.thread_id)          
 
354
        self.store.find(Message,Message.message_id == msgdata['message_id']).set(thread_id = threadID)
 
355
 
 
356
        ## Choose a unique name for the thread
 
357
        # Try to get the name from the to-line, e.g., listname+new+name@hostname
 
358
        if threadBase:
 
359
            threadBase = self.alphanumericOnly(threadBase).lower()
 
360
            if threadBase in lousyThreadNames or not len(threadBase):
 
361
                threadBase = None
 
362
 
 
363
        # If a name wasn't explicitly specified, try to get one from the subject
 
364
        if not threadBase:
 
365
            # No thread name was specified -- try to get from subject line.
 
366
            # If none in subject line, the threadID will be returned
 
367
            threadBase = self.subjectToName(msg['Subject'].encode(), threadID)
 
368
 
 
369
        # Make sure the thread can fit in the field (char(16))
 
370
        threadBase = threadBase[:13]
 
371
        command = "num = self.store.find(Thread,Thread.thread_name == unicode(threadBase,'utf-8')).count()\nself.store.find(Thread,Thread.thread_id == threadID).set(thread_name = threadName)\n"
 
372
        if DEBUG_MODE:
 
373
            syslog('info','DlistUtils:(createThread)executing query:\n%s',command)
 
374
        # If the threadBase is not unique, make threadBase unique by appending a number
 
375
        num = self.store.find(Thread,Thread.thread_name == unicode(threadBase,"utf-8")).count()    
 
376
 
 
377
        if not num:
 
378
            threadName = unicode(threadBase,"utf-8")
 
379
        else:
 
380
            threadName = unicode(threadBase + str(num+1),"utf-8" )
 
381
 
 
382
        self.store.find(Thread,Thread.thread_id == threadID).set(thread_name = threadName)
 
383
 
 
384
        return (threadID, threadName.encode("utf-8"))
 
385
 
 
386
    def email_recepients(self, msg, msgdata, lists, pref):
 
387
        """Finding the email addresses of matching subscribers from lists,and using the list info to email everyone"""
 
388
        returnList = GetEmailAddress(self.mlist, lists)    
 
389
        msgdata['recips'] = returnList
 
390
        self.setFooterText(msg, msgdata, pref)
 
391
        dq = get_switchboard(mm_cfg.DLISTQUEUE_DIR)
 
392
        dq.enqueue(msg, msgdata, listname=self.mlist.internal_name())
 
393
 
 
394
    @decfunc
 
395
    def newThread(self, msg, msgdata, threadBase=None):
 
396
        """Starts a new thread, including creating and enqueueing the initial messages"""
 
397
        id, name = self.createThread(msg, msgdata, threadBase)
 
398
        msgdata['thread_id'] = id
 
399
        msgdata['thread_name'] = name
 
400
 
 
401
        # Delete any other 'To' headings
 
402
        del msg['To']
 
403
        msg['To'] =  '%s+%s@%s' % (self.mlist.internal_name(),
 
404
                                   name,
 
405
                                   self.mlist.host_name)    
 
406
        for i in (1,2):
 
407
            # different emails for different prefs, so we need to queue separately
 
408
            if(i==1):
 
409
                #For condition where preference = True
 
410
                pref=True
 
411
                command = "lists = [(subscriber.mailman_key.encode('utf-8')) for subscriber in result_new_sql]\n"
 
412
                if DEBUG_MODE:
 
413
                    syslog('info', 'DlistUtils:(newThread)executing query:\n%sfor pref = true\n', command)
 
414
            if(i==2):
 
415
                #For condition where preference = False
 
416
                pref=False
 
417
                command = "lists = [(subscriber.mailman_key.encode('utf-8')) for subscriber in result_new_sql]\n"
 
418
                if DEBUG_MODE:
 
419
                    syslog('info', 'DlistUtils:(newThread)executing query:\n%sfor pref = false\n', command)
 
420
 
 
421
            #Execute a SELECT statement, to find the list of matching subscribers.
 
422
            result_new_sql = self.store.find(Subscriber,And(Subscriber.preference == pref,Subscriber.deleted == False,Subscriber.suppress == 0))
 
423
            lists = [(subscriber.mailman_key.encode('utf-8')) for subscriber in result_new_sql]
 
424
            if DEBUG_MODE:
 
425
                syslog('info', 'value of lists: %s\n', lists)
 
426
            self.email_recepients(msg, msgdata, lists, pref)
 
427
 
 
428
        # Make original message go to nobody (but be archived)
 
429
        msgdata['recips'] = []
 
430
 
 
431
    @decfunc
 
432
    def continueThread(self, msg, msgdata, threadReference):
 
433
        """Continue an existing thread, no return value."""
 
434
        subscriber = Subscriber(self.mlist)
 
435
        message = Message(self.mlist)
 
436
        senderID = subscriber.getSubscriber_id(msg, msgdata, safe=1, loose=1)
 
437
        msgdata['message_id'] = message.createMessage(msg, msgdata)
 
438
 
 
439
        pref=True
 
440
 
 
441
        # email selected people
 
442
        #Execute a SELECT statement, to find the list of matching subscribers.
 
443
        command = "lists = [(subscriber.mailman_key.encode('utf-8')) for subscriber in result_continue_sql]\n"
 
444
        if DEBUG_MODE:
 
445
            syslog('info', 'DlistUtils:(continueThread) executing query:\n%s', command)
 
446
 
 
447
        result_pref_one = self.store.find((Subscriber,Override),And(Override.subscriber_id == Subscriber.subscriber_id,Override.thread_id == msgdata['thread_id'],Override.preference == 0))
 
448
        lists_pref_one = [(override.subscriber_id) for (subscriber,override) in result_pref_one]
 
449
 
 
450
        result_pref_zero = self.store.find((Subscriber,Override),And(Override.subscriber_id == Subscriber.subscriber_id,Override.thread_id == msgdata['thread_id'],Override.preference == 1))
 
451
        lists_pref_zero = [(override.subscriber_id) for (subscriber,override) in result_pref_zero]
 
452
 
 
453
        result_continue_sql = self.store.find(Subscriber,And(Subscriber.deleted == False,Subscriber.suppress == 0,Or(And(Subscriber.preference == 1,lists_pref_one == []),And(Subscriber.preference == 0,lists_pref_zero != []))))
 
454
        lists = [(subscriber.mailman_key.encode('utf-8')) for subscriber in result_continue_sql]
 
455
 
 
456
        self.email_recepients(msg, msgdata, lists, pref)
 
457
 
 
458
        # Make original message go to nobody (but be archived)
 
459
        msgdata['recips'] = []
 
460
 
 
461
    @decfunc
 
462
    def threadIDandName(self, threadReference):
 
463
        """Given thread_id or thread_name, determine the other, returning (thread_id, thread_name)"""    
 
464
        try:
 
465
            thread_id = int(thread_reference)
 
466
            try:
 
467
                command = "result = self.store.find(Thread,Thread.thread_id == thread_id)\nthread_name = [(thread.thread_name) for thread in result]\nthread_name = thread_name.encode('utf-8')\n"
 
468
                if DEBUG_MODE:
 
469
                    syslog('info', 'DlistUtils(threadIDandName)Executing query:\n%s', command)
 
470
                result = self.store.find(Thread,Thread.thread_id == thread_id)
 
471
                thread_name = [(thread.thread_name) for thread in result]
 
472
                thread_name = thread_name.encode('utf-8')            
 
473
            except:
 
474
                raise NonexistentThreadRequest("Your message could not be sent because you specified a nonexistent conversation (%d).  Perhaps you meant to start a new conversation, which you can do by addressing your message to %s+new@%s" % (thread_id, self.mlist.real_name, self.mlist.host_name))
 
475
        except:
 
476
            thread_name = threadReference.lower()
 
477
            if DEBUG_MODE:
 
478
                syslog('info', 'thread_name = %s\n', thread_name)
 
479
            command = "result = self.store.find(Thread,Thread.thread_name == unicode(thread_name,'utf-8'))\nthread_id = [(thread.thread_id) for thread in result]\n"
 
480
            if DEBUG_MODE:
 
481
                syslog('info', 'DlistUtils(threadIDandName)Executing query:\n%s', command)
 
482
            result = self.store.find(Thread,Thread.thread_name == unicode(thread_name,'utf-8'))
 
483
            thread_id = [(thread.thread_id) for thread in result]
 
484
            thread_id = thread_id[0]#to convert a list to int
 
485
            if DEBUG_MODE:
 
486
                syslog('info', 'thread_id: %s\n', thread_id)            
 
487
 
 
488
            if thread_id == None:
 
489
                raise NonexistentThreadRequest("Your message could not be sent because you addressed it to a nonexistent conversation (%s).  Perhaps you meant to start a new conversation named %s, which you can do by addressing your message to %s+new+%s@%s" % (thread_name, thread_name, self.mlist.real_name, thread_name, self.mlist.host_name))
 
490
 
 
491
        return (thread_id, thread_name)
 
492
 
 
493
    def getThreadAddress(msgdata):
 
494
        """The list address for any given thread."""
 
495
        return "%s+%s@%s" % (self.mlist.internal_name(), msgdata['thread_name'], self.mlist.host_name)
 
496
 
 
497
    def subscribeToThread(self, msg, msgdata):
 
498
        """Subscribe the sender of a message to a given thread."""
 
499
        override = Override(self.mlist)
 
500
        override.override(msg, msgdata, 1)
 
501
 
 
502
    def unsubscribeFromThread(self, msg, msgdata):
 
503
        """Unubscribe the sender of a message from a given thread."""
 
504
        override = Override(self.mlist)
 
505
        override.override(msg, msgdata, 0)
 
506
 
 
507
    def alphanumericOnly(self,s):
 
508
        """Filter any non-letter characters from a string"""
 
509
        result = [letter for letter in s if letter in string.ascii_letters or letter in string.digits]
 
510
        return string.join(result, '')
 
511
 
 
512
    def subjectToName(self,subject, threadID):
 
513
        """Return a lower-case name for a new thread based on the subject, if present, or on the threadID"""
 
514
        result = None
 
515
 
 
516
        if subject == '':
 
517
            return str(threadID)
 
518
 
 
519
        subjectWords = [self.alphanumericOnly(w) for w in subject.split()]
 
520
 
 
521
        # Choose the longest word of 4 or more characters
 
522
        maxLength = 3
 
523
        maxWord = None
 
524
        for word in subjectWords:
 
525
            if len(word) > maxLength and word not in lousyThreadNames:
 
526
                maxWord = word
 
527
                maxLength = len(word)
 
528
            if maxWord:
 
529
                result = maxWord.lower()
 
530
 
 
531
            if not result:
 
532
                # Choose the first word that's not a stop word or lousy thread name
 
533
                stopWords = ["a", "an", "the", "of", "re", "you", "i", "no", "not", "do", "for"]
 
534
                for word in subjectWords:
 
535
                    if word not in stopWords and word not in lousyThreadNames:
 
536
                        result = word.lower()
 
537
                        break
 
538
 
 
539
            if not result:
 
540
                # If no other candidate, just return the first subject word
 
541
                result = subjectWords[0].lower() + str(threadID)
 
542
 
 
543
        return result[:12]
 
544
 
 
545
    def setFooterText(self, msg, msgdata, preference):
 
546
        msgdata['dlists_preference'] = preference
 
547
        thread_name = msgdata['thread_name']
 
548
        thread_id = msgdata['thread_id']
 
549
        override = Override(self.mlist)
 
550
        web_addr = override.overrideURL(self.mlist.internal_name(), self.mlist.host_name, thread_id, not preference)
 
551
        if preference == 1:
 
552
            subscribe_string = "unsubscribe"
 
553
            preposition = "from"
 
554
        else:
 
555
            subscribe_string = "subscribe"
 
556
            preposition = "to"
 
557
        email_addr = '%s+%s+%s@%s' % (self.mlist.internal_name(), thread_name, subscribe_string, self.mlist.host_name)
 
558
        #if DEBUG_MODE:
 
559
            #syslog('info', "msg['Subject'] = /%s/", msg['Subject'])
 
560
        subject = urllib.quote(msg['Subject'].encode())
 
561
        post_addr = "%s+%s@%s" % (self.mlist.internal_name(), thread_name, self.mlist.host_name)
 
562
        post_addr_with_subject = "%s?Subject=%s" % (post_addr, subject)
 
563
        #if DEBUG_MODE:
 
564
            #syslog('info', 'post_addr_with_subject = %s', post_addr)
 
565
 
 
566
        # Used in Handlers/ToDigest.py
 
567
        msgdata['contribute'] = "To contribute to this conversation, send " \
 
568
                        "your message to <%s+%s@%s>\n" % \
 
569
                        (self.mlist.internal_name(), msgdata['thread_name'], self.mlist.host_name)
 
570
 
 
571
        # Used in ToArchive
 
572
        msgdata['contribute-html'] = 'To contribute to this conversation, send your message to <a href="mailto:%s">%s</a>\n' % (post_addr, post_addr)
 
573
        msgdata['footer-text'] = '\n\nTo %s %s this conversation, send email to <%s> or visit <%s>\nTo contribute to this conversation, use your mailer\'s reply-all or reply-group command or send your message to %s\nTo start a new conversation, send email to <%s+new@%s>\nTo unsubscribe entirely from %s, send email to <%s-request@%s> with subject unsubscribe.' % (subscribe_string, preposition, email_addr, web_addr, post_addr, self.mlist.internal_name(), self.mlist.host_name, self.mlist.internal_name(), self.mlist.internal_name(), self.mlist.host_name)
 
574
        #if DEBUG_MODE:
 
575
            #syslog('info', 'footer-text = /%s/', msgdata['footer-text'])
 
576
        msgdata['footer-html'] = '<br>To %s %s this conversation, send email to <a href="mailto:%s">%s</a> or visit <a href="%s">%s</a>.<br>To contribute to this conversation, use your mailer\'s reply-all or reply-group command or send your message to <a href="mailto:%s?subject=%s">%s</a>.<br>To start a new conversation, send email to <a href="mailto:%s+new@%s">%s+new@%s</a><br>To unsubscribe entirely from %s, send mail to <a href="mailto:%s-request@%s?subject=unsubscribe">%s-request@%s</a> with subject unsubscribe.' % (subscribe_string, preposition, email_addr, email_addr, web_addr, web_addr, post_addr, subject, post_addr, self.mlist.internal_name(), self.mlist.host_name, self.mlist.internal_name(), self.mlist.host_name, self.mlist.internal_name(), self.mlist.internal_name(), self.mlist.host_name, self.mlist.internal_name(), self.mlist.host_name)
576
577
 
577
578
class Alias(object):
578
 
        __storm_table__ = "alias"       
579
 
        pseudonym = Unicode(primary = True)
580
 
        subscriber_id = Int()
581
 
        subscriber = Reference(subscriber_id,Subscriber.subscriber_id)
582
 
 
583
 
        def __init__(self,mlist):
584
 
                self.mlist = mlist
585
 
                self.database = getConn(mlist)
586
 
 
587
 
        @decfunc
588
 
        def canonicalize_sender(self, aliases):
589
 
                """Execute a SELECT statement, returning the email addresses of matching subscribers."""
590
 
 
591
 
                command = "result = self.store.find((Subscriber,Alias),Alias.pseudonym = alias,Subscriber.subscriber_id = Alias.subscriber_id)\nlists = [(subscriber.mailman_key) for subscriber,alias in result]\n"
592
 
                if DEBUG_MODE:
593
 
                         syslog('info', 'DlistUtils(canonicalize_sender)Executing query:\n%s', command)
594
 
 
595
 
                for alias in aliases:
596
 
                        #if DEBUG_MODE:
597
 
                                #syslog('info', 'Checking if <%s> is an alias', alias)
598
 
                        result = self.store.find((Subscriber,Alias),And(Alias.pseudonym == unicode(alias,'utf-8'),Subscriber.subscriber_id == Alias.subscriber_id))
599
 
                        lists = [(subscriber.mailman_key.encode('utf-8')) for (subscriber,alias) in result]
600
 
 
601
 
                        returnList = GetEmailAddress(self.mlist, lists)         
602
 
                        match = returnList
603
 
 
604
 
                        if len(match) == 1:
605
 
                                # I should really confirm that there is only one match
606
 
                                #if DEBUG_MODE:
607
 
                                        #syslog('info', 'Match: %s', match)
608
 
                                if DEBUG_MODE:
609
 
                                        syslog('info', 'Match found: <%s>', match[0])
610
 
                                return match[0]
611
 
                        elif len(match) > 1:
612
 
                                raise DatabaseIntegrityError
613
 
        
614
 
        @decfunc
615
 
        def get_aliases(self, subscriber_id):
616
 
                """Execute a SELECT statement, returning the results as a list."""
617
 
                command = "result = self.store.find(Alias,Alias.subscriber_id == subscriber_id)\nlists = [(alias.pseudonym.encode('utf-8')) for alias in result]\n"
618
 
                if DEBUG_MODE:
619
 
                         syslog('info', 'DlistUtils(get_aliases):Executing query:\n%s', command)
620
 
                if DEBUG_MODE:
621
 
                         syslog('info', 'The value of subscriber_id is: %s\n', subscriber_id)
622
 
                result = self.store.find(Alias,Alias.subscriber_id == subscriber_id)
623
 
                lists = [(alias.pseudonym.encode('utf-8')) for alias in result]
624
 
 
625
 
                return lists
626
 
        
627
 
        @decfunc
628
 
        def change_aliases(self, subscriber_id, oldAliasList, newAliasList):
629
 
                if DEBUG_MODE:
630
 
                         syslog('info', 'executing change_aliases\n')
631
 
 
632
 
                for a in oldAliasList:
633
 
                        if a not in newAliasList:
634
 
                                self.store.find(Alias,Alias.pseudonym == unicode(a)).remove()
635
 
                for a in newAliasList:
636
 
                        if a not in oldAliasList:
637
 
                                self.pseudonym = unicode(a,"utf-8")
638
 
                                self.subscriber_id = subscriber_id
639
 
                                self.store.add(self)
 
579
    __storm_table__ = "alias"    
 
580
    pseudonym = Unicode(primary = True)
 
581
    subscriber_id = Int()
 
582
    subscriber = Reference(subscriber_id,Subscriber.subscriber_id)
 
583
 
 
584
    def __init__(self,mlist):
 
585
        self.mlist = mlist
 
586
        self.database = getConn(mlist)
 
587
 
 
588
    @decfunc
 
589
    def canonicalize_sender(self, aliases):
 
590
        """Execute a SELECT statement, returning the email addresses of matching subscribers."""
 
591
 
 
592
        command = "result = self.store.find((Subscriber,Alias),Alias.pseudonym = alias,Subscriber.subscriber_id = Alias.subscriber_id)\nlists = [(subscriber.mailman_key) for subscriber,alias in result]\n"
 
593
        if DEBUG_MODE:
 
594
             syslog('info', 'DlistUtils(canonicalize_sender)Executing query:\n%s', command)
 
595
 
 
596
        for alias in aliases:
 
597
            #if DEBUG_MODE:
 
598
                #syslog('info', 'Checking if <%s> is an alias', alias)
 
599
            result = self.store.find((Subscriber,Alias),And(Alias.pseudonym == unicode(alias,'utf-8'),Subscriber.subscriber_id == Alias.subscriber_id))
 
600
            lists = [(subscriber.mailman_key.encode('utf-8')) for (subscriber,alias) in result]
 
601
 
 
602
            returnList = GetEmailAddress(self.mlist, lists)        
 
603
            match = returnList
 
604
 
 
605
            if len(match) == 1:
 
606
                # I should really confirm that there is only one match
 
607
                #if DEBUG_MODE:
 
608
                    #syslog('info', 'Match: %s', match)
 
609
                if DEBUG_MODE:
 
610
                    syslog('info', 'Match found: <%s>', match[0])
 
611
                return match[0]
 
612
            elif len(match) > 1:
 
613
                raise DatabaseIntegrityError
 
614
 
 
615
    @decfunc
 
616
    def get_aliases(self, subscriber_id):
 
617
        """Execute a SELECT statement, returning the results as a list."""
 
618
        command = "result = self.store.find(Alias,Alias.subscriber_id == subscriber_id)\nlists = [(alias.pseudonym.encode('utf-8')) for alias in result]\n"
 
619
        if DEBUG_MODE:
 
620
             syslog('info', 'DlistUtils(get_aliases):Executing query:\n%s', command)
 
621
        if DEBUG_MODE:
 
622
             syslog('info', 'The value of subscriber_id is: %s\n', subscriber_id)
 
623
        result = self.store.find(Alias,Alias.subscriber_id == subscriber_id)
 
624
        lists = [(alias.pseudonym.encode('utf-8')) for alias in result]
 
625
 
 
626
        return lists
 
627
 
 
628
    @decfunc
 
629
    def change_aliases(self, subscriber_id, oldAliasList, newAliasList):
 
630
        if DEBUG_MODE:
 
631
             syslog('info', 'executing change_aliases\n')
 
632
        for a in oldAliasList:
 
633
            if a not in newAliasList:
 
634
                self.store.find(Alias,Alias.pseudonym == unicode(a,"utf-8")).remove()
 
635
        for a in newAliasList:
 
636
            if a not in oldAliasList:
 
637
                syslog('info', 'going to add alias in database\n')
 
638
                self.pseudonym = unicode(a,"utf-8")
 
639
                self.subscriber_id = subscriber_id
 
640
                self.store.add(self)
 
641
                syslog('info', 'alias added\n')
640
642
 
641
643
class Override(object):
642
 
        __storm_table__ = "override"
643
 
        __storm_primary__ = "subscriber_id", "thread_id"
644
 
        subscriber_id = Int()
645
 
        subscriber = Reference(subscriber_id,Subscriber.subscriber_id)
646
 
        thread_id = Int()
647
 
        thread = Reference(thread_id,Thread.thread_id)
648
 
        preference = Int()
649
 
 
650
 
        def __init__(self,mlist):
651
 
                self.mlist = mlist
652
 
                self.database = getConn(mlist)
653
 
    
654
 
        def override(self, msg, msgdata, preference):
655
 
                """Subscribe or unsubscribe a user from a given thread."""
656
 
                if DEBUG_MODE:
657
 
                         syslog('info', 'DlistUtils: in override')
658
 
                subscriber = Subscriber(self.mlist)
659
 
                subscriberID = subscriber.getSubscriber_id(msg, msgdata, loose=1) 
660
 
                threadID = msgdata['thread_id']
661
 
                self._override_inner(subscriberID, threadID, preference)
662
 
        
663
 
        def override_from_web(self, subscriberID, thread_reference, preference):
664
 
                """Add an override entry to the database, returning false if unable to fulfil request"""
665
 
                try:
666
 
                        threadNum = int(thread_reference)
667
 
                        preference = int(preference)#TODO this line was not here before
668
 
                except ValueError:
669
 
                        thread = Thread(self.mlist)
670
 
                        threadNum, temp =thread.threadIDandName(thread_reference)
671
 
                return self._override_inner(subscriberID, threadNum, preference)
672
 
            
673
 
        # This is called both by override and override_from_web, above
674
 
        @decfunc
675
 
        def _override_inner(self, subscriberID, threadID, preference):
676
 
                """Add an override entry to the database, returning false if unable to fulfil request"""
677
 
 
678
 
                #if DEBUG_MODE:
679
 
                         #syslog('info', 'DlistUtils: in override_inner')
680
 
                #First check if thread exists   
681
 
                command = "exists = self.store.find(Thread,Thread.thread_id == threadID).count()\nself.store.find(Override,And(Override.subscriber_id == subscriberID,Override.thread_id == threadID)).remove()\noverride = self.store.add(Override())\noverride.subscriber_id = subscriberID\noverride.thread_id = threadID\noverride.preference = preference\n"
682
 
                if DEBUG_MODE:
683
 
                         syslog('info', 'DlistUtils(override_from_web):Executing query:\n%s', command)
684
 
                exists = self.store.find(Thread,Thread.thread_id == threadID).count()           
685
 
 
686
 
                if not exists:
687
 
                        return 0
688
 
    
689
 
                # Remove any prior override by this user
690
 
                self.store.find(Override,And(Override.subscriber_id == subscriberID,Override.thread_id == threadID)).remove()
691
 
        
692
 
                # Now, insert the change
693
 
                self.subscriber_id = subscriberID    
694
 
                self.thread_id = threadID
695
 
                self.preference = preference
696
 
                self.store.add(self)            
697
 
 
698
 
                return 1
699
 
 
700
 
        def overrideURL(self,list_name, host_name, thread_reference, preference):
701
 
                return 'http://%s/mailman/options/%s?override=%s&preference=%d' % (host_name, list_name, thread_reference, preference)
 
644
    __storm_table__ = "override"
 
645
    __storm_primary__ = "subscriber_id", "thread_id"
 
646
    subscriber_id = Int()
 
647
    subscriber = Reference(subscriber_id,Subscriber.subscriber_id)
 
648
    thread_id = Int()
 
649
    thread = Reference(thread_id,Thread.thread_id)
 
650
    preference = Int()
 
651
 
 
652
    def __init__(self,mlist):
 
653
        self.mlist = mlist
 
654
        self.database = getConn(mlist)
 
655
 
 
656
    def override(self, msg, msgdata, preference):
 
657
        """Subscribe or unsubscribe a user from a given thread."""
 
658
        if DEBUG_MODE:
 
659
             syslog('info', 'DlistUtils: in override')
 
660
        subscriber = Subscriber(self.mlist)
 
661
        subscriberID = subscriber.getSubscriber_id(msg, msgdata, loose=1) 
 
662
        threadID = msgdata['thread_id']
 
663
        self._override_inner(subscriberID, threadID, preference)
 
664
 
 
665
    def override_from_web(self, subscriberID, thread_reference, preference):
 
666
        """Add an override entry to the database, returning false if unable to fulfil request"""
 
667
        try:
 
668
            threadNum = int(thread_reference)
 
669
            preference = int(preference)
 
670
        except ValueError:
 
671
            thread = Thread(self.mlist)
 
672
            threadNum, temp =thread.threadIDandName(thread_reference)
 
673
        return self._override_inner(subscriberID, threadNum, preference)
 
674
 
 
675
    # This is called both by override and override_from_web, above
 
676
    @decfunc
 
677
    def _override_inner(self, subscriberID, threadID, preference):
 
678
        """Add an override entry to the database, returning false if unable to fulfil request"""
 
679
 
 
680
        #if DEBUG_MODE:
 
681
             #syslog('info', 'DlistUtils: in override_inner')
 
682
        #First check if thread exists    
 
683
        command = "exists = self.store.find(Thread,Thread.thread_id == threadID).count()\nself.store.find(Override,And(Override.subscriber_id == subscriberID,Override.thread_id == threadID)).remove()\noverride = self.store.add(Override())\noverride.subscriber_id = subscriberID\noverride.thread_id = threadID\noverride.preference = preference\n"
 
684
        if DEBUG_MODE:
 
685
             syslog('info', 'DlistUtils(override_from_web):Executing query:\n%s', command)
 
686
        exists = self.store.find(Thread,Thread.thread_id == threadID).count()        
 
687
 
 
688
        if not exists:
 
689
            return 0
 
690
 
 
691
        # Remove any prior override by this user
 
692
        self.store.find(Override,And(Override.subscriber_id == subscriberID,Override.thread_id == threadID)).remove()
 
693
 
 
694
        # Now, insert the change
 
695
        self.subscriber_id = subscriberID    
 
696
        self.thread_id = threadID
 
697
        self.preference = preference
 
698
        self.store.add(self)        
 
699
 
 
700
        return 1
 
701
 
 
702
    def overrideURL(self,list_name, host_name, thread_reference, preference):
 
703
        return 'http://%s/mailman/options/%s?override=%s&preference=%d' % (host_name, list_name, thread_reference, preference)
702
704
 
703
705
 
704
706
#Functions Independent of the database tables in DlistUtils
705
707
 
706
708
def GetEmailAddress(mlist, lists):
707
 
        """Extract email address from list of matching subscribers"""
708
 
        returnList = []
709
 
 
710
 
        for e in lists:
711
 
                try:
712
 
                        newMember = mlist.getMemberCPAddress(e)
713
 
                        returnList.append(newMember)
714
 
                except:
715
 
                        #syslog('error', 'Unable to find user ' + e + ' in internal database for %s', mlist.internal_name());
716
 
                        syslog('error', 'User name was found through this query: ' + command);
717
 
        if DEBUG_MODE:
718
 
                 syslog('info', 'Result is: %s', returnList)
719
 
        return returnList
720
 
    
 
709
    """Extract email address from list of matching subscribers"""
 
710
    returnList = []
 
711
 
 
712
    for e in lists:
 
713
        try:
 
714
            newMember = mlist.getMemberCPAddress(e)
 
715
            returnList.append(newMember)
 
716
        except:
 
717
            #syslog('error', 'Unable to find user ' + e + ' in internal database for %s', mlist.internal_name());
 
718
            syslog('error', 'User name was found through this query: ' + command);
 
719
        if DEBUG_MODE:
 
720
             syslog('info', 'Result is: %s', returnList)
 
721
    return returnList
 
722
 
721
723
def enabled(mlist):
722
 
        """Check whether a given mailing list has dynamic sublists enabled"""
723
 
        try:
724
 
                return mlist.dlists_enabled  
725
 
        except:
726
 
                return True #TODO was 0 before
 
724
    """Check whether a given mailing list has dynamic sublists enabled"""
 
725
    try:
 
726
        return mlist.dlists_enabled  
 
727
    except:
 
728
        return True
727
729
 
728
730
def getConn(mlist):
729
 
        
730
 
        """Return a connection to the database for a given mailing list.The argument may be either the mlist object or a string."""
731
 
        if not mlist:
732
 
                uri = mm_cfg.STORM_DB + '://' + mm_cfg.STORM_DB_USER + ':' + mm_cfg.STORM_DB_PASS + '@' + mm_cfg.STORM_DB_HOST + '/' + mm_cfg.STORM_DB
733
 
                return create_database(uri)
734
 
        
735
 
        try:
736
 
                name = mlist.internal_name()
737
 
        except:
738
 
                name = mlist.lower()
739
 
        uri = mm_cfg.STORM_DB + '://' + mm_cfg.STORM_DB_USER + ':' + mm_cfg.STORM_DB_PASS + '@' + mm_cfg.STORM_DB_HOST + '/' + name
740
 
        return create_database(uri)
741
 
        
 
731
    """Return a connection to the database for a given mailing list.The argument may be either the mlist object or a string."""
 
732
    if not mlist:
 
733
        uri = mm_cfg.STORM_DB + '://' + mm_cfg.STORM_DB_USER + ':' + mm_cfg.STORM_DB_PASS + '@' + mm_cfg.STORM_DB_HOST + '/' +  mm_cfg.STORM_DB
 
734
        return create_database(uri)
 
735
 
 
736
    try:
 
737
        name = mlist.internal_name()
 
738
    except:
 
739
        name = mlist.lower()
 
740
    uri = mm_cfg.STORM_DB + '://' + mm_cfg.STORM_DB_USER + ':' + mm_cfg.STORM_DB_PASS + '@' + mm_cfg.STORM_DB_HOST + '/' + name
 
741
    return create_database(uri)
 
742
 
742
743
def _create_database(name):
743
 
        """Create a database. This requires us to not be in a transaction."""
744
 
        conn = pgdb.connect(host='localhost', database='postgres',user='mailman', password='mailman')
745
 
        old_iso_lvl = conn.isolation_level
746
 
        conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
747
 
        cursor = conn.cursor()
748
 
        cursor.execute('create database "%s"' % name) 
749
 
        conn.set_isolation_level(old_iso_lvl)
 
744
    """Create a database. This requires us to not be in a transaction."""
 
745
    conn = pgdb.connect(host='localhost', database='postgres',user='mailman', password='mailman')
 
746
    old_iso_lvl = conn.isolation_level
 
747
    conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
 
748
    cursor = conn.cursor()
 
749
    cursor.execute('create database "%s"' % name) 
 
750
    conn.set_isolation_level(old_iso_lvl)
750
751
 
751
752
def executeSQL(mlist, commands):
752
 
        """Execute a sequence of SQL commands that create tables in the database."""
753
 
        database = getConn(mlist)
754
 
        store = Store(database) 
755
 
 
756
 
        # In case it's called with a string
757
 
        if (type(commands) == types.StringType):
758
 
                commands = [commands]
759
 
        for command in commands:
760
 
                if DEBUG_MODE:
761
 
                         syslog('info', "DlistUtils:(executeSQL)executing query:%s\n", command)
762
 
                try:
763
 
                        store.execute(command)
764
 
                except:
765
 
                        pass
766
 
 
767
 
        store.commit()
768
 
        store.close()
 
753
    """Execute a sequence of SQL commands that create tables in the database."""
 
754
    database = getConn(mlist)
 
755
    store = Store(database)    
 
756
    # In case it's called with a string
 
757
    if(type(commands) == types.StringType):
 
758
        commands = [commands]
 
759
    for command in commands:
 
760
        if DEBUG_MODE:
 
761
            syslog('info', "DlistUtils:(executeSQL)executing query:%s\n", command)
 
762
        try:
 
763
            store.execute(command)
 
764
        except:
 
765
            pass
 
766
 
 
767
    store.commit()
 
768
    store.close()
769
769
 
770
770
def create_dlist(mlist):
771
 
        """ Set the dynamic sublist options for a mailing list and create the corresponding postgres database and tables."""
772
 
        
773
 
        _create_database(mlist.internal_name())
774
 
        if DEBUG_MODE:
775
 
                 syslog('info', "Database created %s:\n", mlist.internal_name())
776
 
        
777
 
        executeSQL(mlist, 
778
 
               ["CREATE TABLE subscriber (subscriber_id SERIAL PRIMARY KEY,\
779
 
                                          mailman_key VARCHAR(255) UNIQUE,\
780
 
                                          preference INT2 DEFAULT 1,\
781
 
                                          format INT2 DEFAULT 3, \
782
 
                                          deleted BOOLEAN DEFAULT FALSE, \
783
 
                                          suppress INT2 DEFAULT 0)",
784
 
                "CREATE UNIQUE INDEX subscriber_mailman_key ON subscriber(mailman_key)",
785
 
                "CREATE TABLE message (message_id SERIAL PRIMARY KEY,\
786
 
                                       sender_id INTEGER REFERENCES subscriber,\
787
 
                                       subject VARCHAR(255),\
788
 
                                       thread_id INTEGER DEFAULT NULL)",
789
 
                "CREATE TABLE thread (thread_id SERIAL PRIMARY KEY,\
790
 
                                      thread_name CHAR(16),\
791
 
                                      base_message_id INTEGER REFERENCES message,\
792
 
                                      status INT2 DEFAULT 0,\
793
 
                                      parent INTEGER DEFAULT NULL)",
794
 
                "CREATE TABLE alias (pseudonym VARCHAR(255) PRIMARY KEY, \
795
 
                                     subscriber_id INTEGER REFERENCES subscriber)",
796
 
                "CREATE TABLE override (subscriber_id INTEGER REFERENCES subscriber,\
797
 
                                        thread_id INTEGER NOT NULL REFERENCES thread,\
798
 
                                        preference INT2 NOT NULL)"])    
799
 
 
800
 
        
801
 
        database = getConn(mlist)
802
 
        store = Store(database)
803
 
 
804
 
        # Needed in case non-subscribers post
805
 
        subscriber = Subscriber(mlist)
806
 
        subscriber.subscriber_id = 0
807
 
        subscriber.suppress = 1
808
 
        #"INSERT INTO subscriber (subscriber_id, suppress) VALUES (0,1)")
809
 
        store.add(subscriber)
810
 
 
811
 
        store.commit()
812
 
        store.close()
813
 
        
814
 
        alreadyLocked = mlist.Locked()
815
 
        if alreadyLocked == 0:
816
 
                mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT)
817
 
        mlist.dlists_enabled = True #TODO was 1 before
818
 
        mlist.require_explicit_destination = 0
819
 
        mlist.include_list_post_header = 0
820
 
        mlist.include_rfc2369_headers = 0
821
 
        mlist.loose_email_matching = 1
822
 
        mlist.obscure_addresses = 0
823
 
 
824
 
        mlist.Save()
825
 
        if alreadyLocked == 0:
826
 
                mlist.Unlock()
 
771
    """ Set the dynamic sublist options for a mailing list and create the corresponding postgres database and tables."""
 
772
 
 
773
    _create_database(mlist.internal_name())
 
774
    if DEBUG_MODE:
 
775
         syslog('info', "Database created: %s\n", mlist.internal_name())
 
776
 
 
777
    executeSQL(mlist, 
 
778
            ["CREATE TABLE subscriber (subscriber_id SERIAL PRIMARY KEY,\
 
779
                                       mailman_key VARCHAR(255) UNIQUE,\
 
780
                                       preference INT2 DEFAULT 1,\
 
781
                                       format INT2 DEFAULT 3, \
 
782
                                       deleted BOOLEAN DEFAULT FALSE, \
 
783
                                       suppress INT2 DEFAULT 0)",
 
784
             "CREATE UNIQUE INDEX subscriber_mailman_key ON subscriber(mailman_key)",
 
785
             "CREATE TABLE message (message_id SERIAL PRIMARY KEY,\
 
786
                                   sender_id INTEGER REFERENCES subscriber,\
 
787
                                   subject VARCHAR(255),\
 
788
                                   thread_id INTEGER DEFAULT NULL)",
 
789
             "CREATE TABLE thread (thread_id SERIAL PRIMARY KEY,\
 
790
                                  thread_name CHAR(16),\
 
791
                                  base_message_id INTEGER REFERENCES message,\
 
792
                                  status INT2 DEFAULT 0,\
 
793
                                  parent INTEGER DEFAULT NULL)",
 
794
             "CREATE TABLE alias (pseudonym VARCHAR(255) PRIMARY KEY, \
 
795
                    subscriber_id INTEGER REFERENCES subscriber)",
 
796
             "CREATE TABLE override (subscriber_id INTEGER REFERENCES subscriber,\
 
797
                                    thread_id INTEGER NOT NULL REFERENCES thread,\
 
798
                                    preference INT2 NOT NULL)"])
 
799
 
 
800
 
 
801
    database = getConn(mlist)
 
802
    store = Store(database)
 
803
 
 
804
    # Needed in case non-subscribers post
 
805
    subscriber = Subscriber(mlist)
 
806
    subscriber.subscriber_id = 0
 
807
    subscriber.suppress = 1
 
808
    #"INSERT INTO subscriber (subscriber_id, suppress) VALUES (0,1)")
 
809
    store.add(subscriber)
 
810
 
 
811
    store.commit()
 
812
    store.close()
 
813
 
 
814
    alreadyLocked = mlist.Locked()
 
815
    if alreadyLocked == 0:
 
816
        mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT)
 
817
    mlist.dlists_enabled = True
 
818
    mlist.require_explicit_destination = 0
 
819
    mlist.include_list_post_header = 0
 
820
    mlist.include_rfc2369_headers = 0
 
821
    mlist.loose_email_matching = 1
 
822
    mlist.obscure_addresses = 0
 
823
 
 
824
    mlist.Save()
 
825
    if alreadyLocked == 0:
 
826
        mlist.Unlock()