Mercurial Hosting > nabble
diff src/nabble/model/MailingListImpl.java @ 0:7ecd1a4ef557
add content
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Thu, 21 Mar 2019 19:15:52 -0600 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/nabble/model/MailingListImpl.java Thu Mar 21 19:15:52 2019 -0600 @@ -0,0 +1,504 @@ +package nabble.model; + +import fschmidt.db.DbDatabase; +import fschmidt.db.DbNull; +import fschmidt.db.DbObject; +import fschmidt.db.DbObjectFactory; +import fschmidt.db.DbRecord; +import fschmidt.db.DbTable; +import fschmidt.db.DbUtils; +import fschmidt.db.ListenerList; +import fschmidt.db.LongKey; +import fschmidt.util.java.Computable; +import fschmidt.util.java.SimpleCache; +import fschmidt.util.mail.Mail; +import fschmidt.util.mail.MailAddress; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.sql.Connection; +import java.sql.Statement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Random; +import java.util.WeakHashMap; +import java.util.List; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public final class MailingListImpl implements MailingList, DbObject<LongKey, MailingListImpl> { + private static final Logger logger = LoggerFactory.getLogger(MailingListImpl.class); + + final SiteKey siteKey; + private final DbRecord<LongKey, MailingListImpl> record; + private String listAddress; + private String listName; + private String url; + private String emailId; + private boolean ignoreNoArchive; + private boolean plainTextOnly; + private ListServer listServer; + private String exportOwner; + + private NodeImpl forum; + + private MailingListImpl(SiteKey siteKey,LongKey key, ResultSet rs) + throws SQLException + { + this.siteKey = siteKey; + record = table(siteKey).newRecord(this, key); + listAddress = rs.getString("list_address"); + emailId = rs.getString("email_id"); + listName = rs.getString("list_name"); + url = rs.getString("list_home_url"); + ignoreNoArchive = rs.getBoolean("ignore_no_archive"); + plainTextOnly = rs.getBoolean("plain_text_only"); + listServer = ListServer.getServer(rs.getString("list_server")); + exportOwner = rs.getString("export_owner"); + } + + MailingListImpl(NodeImpl forum, ListServer listServer, String listAddress, String url) + throws ModelException + { + this.siteKey = forum.siteKey; + record = table(siteKey).newRecord(this); + long id = forum.getId(); + record.fields().put("node_id", id); + this.forum = forum; + setListServer(listServer); + setListAddress(listAddress); + setUrl(url); + record.insert(); + } + + public DbRecord<LongKey, MailingListImpl> getDbRecord() { + return record; + } + + private DbTable<LongKey,MailingListImpl> table() { + return record.getDbTable(); + } + + private DbDatabase db() { + return table().getDbDatabase(); + } + + public long getId() { + return record.getPrimaryKey().value(); + } + + NodeImpl getForumImpl() { + if (DbUtils.isStale(forum)) { + forum = NodeImpl.getNode(siteKey,getId()); + } + return forum; + } + + public Node getForum() { + return getForumImpl(); + } + + public String getListAddress() { + return listAddress; + } + + private static MailingListImpl getMailingList(SiteKey siteKey,long id) { + DbTable<LongKey,MailingListImpl> tbl = table(siteKey); + return tbl==null ? null : tbl.findByPrimaryKey(new LongKey(id)); + } + + public void delete() { + if (!db().isInTransaction()) { + db().beginTransaction(); + try { + MailingListImpl mailingList = DbUtils.getGoodCopy(this); + mailingList.delete(); + db().commitTransaction(); + } finally { + db().endTransaction(); + } + return; + } + record.delete(); + } + + static MailingListImpl getMailingListForForum(NodeImpl forum) { + MailingListImpl mailingList = table(forum.siteKey).findByPrimaryKey(new LongKey(forum.getId())); + if (mailingList != null) { + mailingList.forum = forum; + } + return mailingList; + } + + private static class Lazy { + static final String emailPrefix; + static final String emailSuffix; + static final Pattern EMAIL_SUFFIXPATTERN; + static final Pattern pattern; + static { + String addrSpec = MailingLists.pop3Server.getUsername(); + int ind = addrSpec.indexOf('@'); + emailPrefix = addrSpec.substring(0, ind) + "+"; + emailSuffix = addrSpec.substring(ind); + EMAIL_SUFFIXPATTERN = Pattern.compile( + Pattern.quote(emailPrefix) + "([^@]+)" + Pattern.quote(emailSuffix), + Pattern.CASE_INSENSITIVE); + pattern = Pattern.compile( + Pattern.quote(emailPrefix) + "s(\\d+)n(\\d+)h(\\d+)" + Pattern.quote(emailSuffix), + Pattern.CASE_INSENSITIVE); + } + } + + static MailingListImpl getMailingListByEnvelopeAddress(String address) { + Matcher matcher = Lazy.pattern.matcher(address); + if( matcher.matches() ) { + long siteId = Long.valueOf(matcher.group(1)); + SiteImpl site = SiteKey.getInstance(siteId).site(); + if( site == null ) + return null; + long nodeId = Long.valueOf(matcher.group(2)); + NodeImpl node = site.getNodeImpl(nodeId); + if( node == null ) + return null; + MailingListImpl ml = node.getMailingListImpl(); + if( ml==null ) + return null; + String hash = matcher.group(3); + if( !ml.generateHash().equals(hash) ) + return null; + return ml; + } + matcher = Lazy.EMAIL_SUFFIXPATTERN.matcher(address); + if( matcher.matches() ) { + String emailId = matcher.group(1); + return getMailingListByOldEmailId(emailId); + } + return null; + } + + private static MailingListImpl getMailingListByOldEmailId(String emailId) { + try { + Connection con = Db.dbGlobal().getConnection(); + PreparedStatement stmt = con.prepareStatement( + "select * from mailing_list_lookup where email_id = ?" + , ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_UPDATABLE + ); + stmt.setString(1, emailId.trim().toLowerCase()); + ResultSet rs = stmt.executeQuery(); + try { + if( !rs.next() ) + return null; + long siteId = rs.getLong("site_id"); + long nodeId = rs.getLong("node_id"); + SiteKey siteKey = SiteKey.getInstance(siteId); + MailingListImpl ml = getMailingList(siteKey,nodeId); + if( ml == null ) { + logger.error("couldn't find mailing list site="+siteId+" node="+nodeId); + rs.deleteRow(); + return null; + } + return ml; + } finally { + rs.close(); + stmt.close(); + con.close(); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public static int cleanMailingListLookup() { + List<String> emailIds = new ArrayList<String>(); + try { + Connection con = Db.dbGlobal().getConnection(); + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery( + "select email_id from mailing_list_lookup" + ); + while( rs.next() ) { + String emailId = rs.getString("email_id"); + emailIds.add(emailId); + } + rs.close(); + stmt.close(); + con.close(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + int count = 0; + for( String emailId : emailIds ) { + MailingList ml = getMailingListByOldEmailId(emailId); + if (ml == null) + count++; + } + return count; + } + + public static List<MailingList> getOldMailingLists() { + List<String> emailIds = new ArrayList<String>(); + try { + Connection con = Db.dbGlobal().getConnection(); + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery( + "select email_id from mailing_list_lookup" + ); + while( rs.next() ) { + String emailId = rs.getString("email_id"); + emailIds.add(emailId); + } + rs.close(); + stmt.close(); + con.close(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + List<MailingList> list = new ArrayList<MailingList>(); + for( String emailId : emailIds ) { + MailingList ml = getMailingListByOldEmailId(emailId); + if (ml != null) + list.add(ml); + } + return list; + } + + private String generateHash() { + int h = 31*(int)getId(); + return Integer.toString(Math.abs(h)%100); + } + + public void setListAddress(String listAddress) throws ModelException.EmailFormat { + listAddress = listAddress.trim(); + UserImpl.validateEmail(listAddress); + this.listAddress = listAddress; + record.fields().put("list_address", listAddress); + } + + public String getListName() { + return listName; + } + + public void setListName(String listName) { + if( listName != null ) { + listName = listName.trim(); + if( listName.equals("") ) + listName = null; + } + this.listName = listName; + record.fields().put("list_name", DbNull.fix(listName)); + } + + String fixSubject(String subject) { + if( listName != null && subject != null ) { + if( subject.startsWith(listName) ) + return subject.substring(listName.length()).trim(); + String lowerListName = listName.toLowerCase(); + String lowerSubject = subject.toLowerCase(); + if( lowerSubject.startsWith( "re: " + lowerListName + " re: " ) ) + return subject.substring(listName.length()+5); + if( lowerSubject.startsWith( "re: " + lowerListName ) ) + return subject.substring(0,4) + subject.substring(4+listName.length()).trim(); + } + return subject; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) throws ModelException.UrlFormat { + url = url.trim(); + validateUrl(url); + this.url = url; + record.fields().put("list_home_url", url); + } + + static void validateUrl(String url) throws ModelException.UrlFormat { + try { + new URL(url); + } catch(MalformedURLException e) { + throw new ModelException.UrlFormat(url,e); + } + } + + public boolean ignoreNoArchive() { + return ignoreNoArchive; + } + + public void setIgnoreNoArchive(boolean ignoreNoArchive) { + this.ignoreNoArchive = ignoreNoArchive; + record.fields().put("ignore_no_archive", ignoreNoArchive); + } + + public boolean plainTextOnly() { + return plainTextOnly; + } + + public void setPlainTextOnly(boolean plainTextOnly) { + this.plainTextOnly = plainTextOnly; + record.fields().put("plain_text_only", plainTextOnly); + } + + public ListServer getListServer() { + return listServer; + } + + public void setListServer(ListServer listServer) { + this.listServer = listServer; + record.fields().put("list_server", listServer.getType()); + } + + public String getEmailId() { + return emailId; + } + + public void setEmailId(String emailId) { + this.emailId = emailId; + record.fields().put("email_id", DbNull.fix(emailId)); + } + + public void update() { + record.update(); + } + + public boolean equals(Object obj) { + return obj instanceof MailingList && ((MailingList) obj).getId() == getId(); + } + + public int hashCode() { + return (int) getId(); + } + + public String toString() { + return "mailing_list-" + getId(); + } + + public ImportResult importMbox(File file, String mailErrorsTo, int maxErrors) + throws ModelException + { + ModelHome.beginImport(); + try { + return MailingLists.importMbox(file, this, mailErrorsTo, maxErrors); + } finally { + ModelHome.endImport(); + } + } + + public MailAddress getSubscriberAddress() { + return new MailAddress( Lazy.emailPrefix + + (emailId != null ? emailId + : "s" + siteKey.getId() + + 'n' + getId() + + 'h' + generateHash() + ) + + Lazy.emailSuffix ); + } + + public String getPassword(User user) { + Random r = new Random(getId() + user.getId()); + long p = Math.round(r.nextDouble() * Math.pow(Character.MAX_RADIX, 8)); + return Long.toString(p, Character.MAX_RADIX); + } + + public String getExportOwner() { + return exportOwner; + } + + public void setExportOwner(String email) throws ModelException.EmailFormat { + if( email != null ) { + email = email.trim(); + UserImpl.validateEmail(email); + } + this.exportOwner = email; + record.fields().put("export_owner", DbNull.fix(this.exportOwner)); + } + + public Node getNodeFromMessageID(String messageID) { + return forum.getNodeImplFromMessageID(messageID); + } + + public void subscribe() { + if (listServer.canSubscribe()) + ModelHome.send(subscribeMail()); + } + + public void unsubscribe() { + if (listServer.canSubscribe()) + ModelHome.send(unsubscribeMail()); + } + + public Mail subscribeMail() { + return listServer.subscribeMail(getSubscriberAddress(), getListAddress(), null, true); + } + + public Mail subscribeMail(User user) { + MailAddress userAddress = new MailAddress(user.getEmail(), user.getName()); + return listServer.subscribeMail(userAddress, getListAddress(), getPassword(user), false); + } + + public Mail unsubscribeMail() { + return listServer.unsubscribeMail(getSubscriberAddress(), getListAddress(), null); + } + + public Mail unsubscribeMail(User user) { + MailAddress userAddress = new MailAddress(user.getEmail(), user.getName()); + return listServer.unsubscribeMail(userAddress, getListAddress(), null); + } + + public Mail defaultsMail(User user, String password) { + MailAddress userAddress = new MailAddress(user.getEmail(), user.getName()); + return listServer.defaultsMail(userAddress, getListAddress(), password); + } + + + static final ListenerList<MailingListImpl> preUpdateListeners = new ListenerList<MailingListImpl>(); + static final ListenerList<MailingListImpl> postInsertListeners = new ListenerList<MailingListImpl>(); + static final ListenerList<MailingListImpl> postUpdateListeners = new ListenerList<MailingListImpl>(); + static final ListenerList<MailingListImpl> postDeleteListeners = new ListenerList<MailingListImpl>(); + + private static Computable<SiteKey,DbTable<LongKey,MailingListImpl>> tables = new SimpleCache<SiteKey,DbTable<LongKey,MailingListImpl>>(new WeakHashMap<SiteKey,DbTable<LongKey,MailingListImpl>>(), new Computable<SiteKey,DbTable<LongKey,MailingListImpl>>() { + public DbTable<LongKey,MailingListImpl> get(SiteKey siteKey) { + DbDatabase db; + try { + db = siteKey.getDb(); + } catch(Db.NoSchema e) { + return null; + } + final long siteId = siteKey.getId(); + DbTable<LongKey,MailingListImpl> table = db.newTable("mailing_list",db.newAssignedLongKeySetter("node_id") + , new DbObjectFactory<LongKey,MailingListImpl>() { + public MailingListImpl makeDbObject(LongKey key,ResultSet rs,String tableName) + throws SQLException + { + SiteKey siteKey = SiteKey.getInstance(siteId); + return new MailingListImpl(siteKey,key,rs); + } + } + ); + table.getPreUpdateListeners().add(preUpdateListeners); + table.getPostInsertListeners().add(postInsertListeners); + table.getPostUpdateListeners().add(postUpdateListeners); + table.getPostDeleteListeners().add(postDeleteListeners); + return table; + } + }); + + private static DbTable<LongKey,MailingListImpl> table(SiteKey siteKey) { + return tables.get(siteKey); + } + + public void rethread() { + try { + MailingLists.rethreadForum( getForumImpl(), false ); + } catch(SQLException e) { + throw new RuntimeException(e); + } + } + +}