diff options
Diffstat (limited to 'subsonic-backend/src/main/java/net/sourceforge/subsonic/backend/service')
3 files changed, 331 insertions, 0 deletions
diff --git a/subsonic-backend/src/main/java/net/sourceforge/subsonic/backend/service/EmailSession.java b/subsonic-backend/src/main/java/net/sourceforge/subsonic/backend/service/EmailSession.java new file mode 100644 index 00000000..cfd96651 --- /dev/null +++ b/subsonic-backend/src/main/java/net/sourceforge/subsonic/backend/service/EmailSession.java @@ -0,0 +1,108 @@ +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2010 (C) Sindre Mehus + */ +package net.sourceforge.subsonic.backend.service; + +import net.sourceforge.subsonic.backend.Util; + +import javax.mail.Folder; +import javax.mail.Session; +import javax.mail.Message; +import javax.mail.Address; +import javax.mail.MessagingException; +import javax.mail.Store; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.AddressException; +import java.util.Properties; +import java.util.List; + +/** + * @author Sindre Mehus + */ +public class EmailSession { + + private static final String SMTP_MAIL_SERVER = "smtp.gmail.com"; + private static final String POP_MAIL_SERVER = "pop.gmail.com"; + private static final String IMAP_MAIL_SERVER = "imap.gmail.com"; + private static final String USER = "subsonic@activeobjects.no"; + + private Session session; + private String password; + + public EmailSession() throws Exception { + Properties props = new Properties(); +// props.setProperty("mail.debug", "true"); + props.setProperty("mail.store.protocol", "pop3s"); + props.setProperty("mail.smtps.host", SMTP_MAIL_SERVER); + props.setProperty("mail.smtps.auth", "true"); + props.setProperty("mail.smtps.timeout", "10000"); + props.setProperty("mail.smtps.connectiontimeout", "10000"); + props.setProperty("mail.pop3s.timeout", "10000"); + props.setProperty("mail.pop3s.connectiontimeout", "10000"); + + session = Session.getDefaultInstance(props, null); + password = Util.getPassword("gmailpwd.txt"); + } + + public void sendMessage(String from, List<String> to, List<String> cc, List<String> bcc, List<String> replyTo, + String subject, String text) throws MessagingException { + Message message = new MimeMessage(session); + + message.setFrom(new InternetAddress(from)); + message.setReplyTo(new Address[]{new InternetAddress(from)}); + message.setRecipients(Message.RecipientType.TO, convertAddress(to)); + message.setRecipients(Message.RecipientType.CC, convertAddress(cc)); + message.setRecipients(Message.RecipientType.BCC, convertAddress(bcc)); + message.setReplyTo(convertAddress(replyTo)); + message.setSubject(subject); + message.setText(text); + + // Send the message + Transport transport = null; + try { + transport = session.getTransport("smtps"); + transport.connect(USER, password); + transport.sendMessage(message, message.getAllRecipients()); + } finally { + if (transport != null) { + transport.close(); + } + } + } + + public Folder getFolder(String name) throws Exception { + Store store = session.getStore("imaps"); + store.connect(IMAP_MAIL_SERVER, USER, password); + Folder folder = store.getFolder(name); + folder.open(Folder.READ_ONLY); + return folder; + } + + private Address[] convertAddress(List<String> addresses) throws AddressException { + if (addresses == null) { + return null; + } + Address[] result = new Address[addresses.size()]; + for (int i = 0; i < addresses.size(); i++) { + result[i] = new InternetAddress(addresses.get(i)); + } + return result; + } +} diff --git a/subsonic-backend/src/main/java/net/sourceforge/subsonic/backend/service/LicenseGenerator.java b/subsonic-backend/src/main/java/net/sourceforge/subsonic/backend/service/LicenseGenerator.java new file mode 100644 index 00000000..77376a8e --- /dev/null +++ b/subsonic-backend/src/main/java/net/sourceforge/subsonic/backend/service/LicenseGenerator.java @@ -0,0 +1,170 @@ +package net.sourceforge.subsonic.backend.service; + +import net.sourceforge.subsonic.backend.dao.PaymentDao; +import net.sourceforge.subsonic.backend.domain.Payment; +import org.apache.commons.codec.binary.Hex; +import org.apache.log4j.Logger; + +import javax.mail.MessagingException; +import java.security.MessageDigest; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Runs a task at regular intervals, checking for incoming donations and sending + * out license keys by email. + * + * @author Sindre Mehus + * @version $Id$ + */ +public class LicenseGenerator { + + private static final Logger LOG = Logger.getLogger(LicenseGenerator.class); + private static final long DELAY = 60; // One minute. + + private PaymentDao paymentDao; + private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + + public void init() { + Runnable task = new Runnable() { + public void run() { + try { + LOG.info("Starting license generator."); + processPayments(); + LOG.info("Completed license generator."); + } catch (Throwable x) { + LOG.error("Failed to process license emails.", x); + } + } + }; + executor.scheduleWithFixedDelay(task, DELAY, DELAY, TimeUnit.SECONDS); + LOG.info("Scheduled license generator to run every " + DELAY + " seconds."); + } + + private void processPayments() throws Exception { + List<Payment> payments = paymentDao.getPaymentsByProcessingStatus(Payment.ProcessingStatus.NEW); + LOG.info(payments.size() + " new payment(s)."); + if (payments.isEmpty()) { + return; + } + + EmailSession emailSession = new EmailSession(); + for (Payment payment : payments) { + processPayment(payment, emailSession); + } + } + + private void processPayment(Payment payment, EmailSession emailSession) { + try { + LOG.info("Processing " + payment); + String email = payment.getPayerEmail(); + if (email == null) { + throw new Exception("Missing email address."); + } + + boolean eligible = isEligible(payment); + boolean ignorable = isIgnorable(payment); + if (eligible) { + sendLicenseTo(email, emailSession); + LOG.info("Sent license key for " + payment); + } else { + LOG.info("Payment not eligible for " + payment); + } + + if (eligible || ignorable) { + payment.setProcessingStatus(Payment.ProcessingStatus.COMPLETED); + payment.setLastUpdated(new Date()); + paymentDao.updatePayment(payment); + } + + } catch (Throwable x) { + LOG.error("Failed to process " + payment, x); + } + } + + private boolean isEligible(Payment payment) { + String status = payment.getPaymentStatus(); + if ("echeck".equalsIgnoreCase(payment.getPaymentType())) { + return "Pending".equalsIgnoreCase(status) || "Completed".equalsIgnoreCase(status); + } + return "Completed".equalsIgnoreCase(status); + } + + private boolean isIgnorable(Payment payment) { + String status = payment.getPaymentStatus(); + return "Denied".equalsIgnoreCase(status) || + "Reversed".equalsIgnoreCase(status) || + "Refunded".equalsIgnoreCase(status); + } + + public void sendLicenseTo(String to, EmailSession emailSession) throws MessagingException { + emailSession.sendMessage("subsonic_donation@activeobjects.no", + Arrays.asList(to), + null, + Arrays.asList("subsonic_donation@activeobjects.no", "sindre@activeobjects.no"), + Arrays.asList("subsonic_donation@activeobjects.no"), + "Subsonic License", + createLicenseContent(to)); + LOG.info("Sent license to " + to); + } + + private String createLicenseContent(String to) { + String license = md5Hex(to.toLowerCase()); + + return "Dear Subsonic donor,\n" + + "\n" + + "Many thanks for your kind donation to Subsonic!\n" + + "Please find your license key below.\n" + + "\n" + + "Email: " + to + "\n" + + "License: " + license + " \n" + + "\n" + + "To install the license key, click the \"Donate\" link in the top right corner of the Subsonic web interface.\n" + + "\n" + + "More info here: http://subsonic.org/pages/getting-started.jsp#3\n" + + "\n" + + "This license is valid for personal, non-commercial of Subsonic. For commercial use, please contact us for licensing options.\n" + + "\n" + + "Thanks again for supporting the project!\n" + + "\n" + + "Best regards,\n" + + "The Subsonic team"; + } + + /** + * Calculates the MD5 digest and returns the value as a 32 character hex string. + * + * @param s Data to digest. + * @return MD5 digest as a hex string. + */ + private String md5Hex(String s) { + if (s == null) { + return null; + } + + try { + MessageDigest md5 = MessageDigest.getInstance("MD5"); + return new String(Hex.encodeHex(md5.digest(s.getBytes("UTF-8")))); + } catch (Exception x) { + throw new RuntimeException(x.getMessage(), x); + } + } + + public void setPaymentDao(PaymentDao paymentDao) { + this.paymentDao = paymentDao; + } + + public static void main(String[] args) throws Exception { + String address = args[0]; +// String license = md5Hex(address.toLowerCase()); +// System.out.println("Email: " + address); +// System.out.println("License: " + license); + + LicenseGenerator generator = new LicenseGenerator(); + generator.sendLicenseTo(address, new EmailSession()); + } +} diff --git a/subsonic-backend/src/main/java/net/sourceforge/subsonic/backend/service/WhitelistGenerator.java b/subsonic-backend/src/main/java/net/sourceforge/subsonic/backend/service/WhitelistGenerator.java new file mode 100644 index 00000000..eef29611 --- /dev/null +++ b/subsonic-backend/src/main/java/net/sourceforge/subsonic/backend/service/WhitelistGenerator.java @@ -0,0 +1,53 @@ +package net.sourceforge.subsonic.backend.service; + +import net.sourceforge.subsonic.backend.dao.PaymentDao; +import org.apache.log4j.Logger; + +import javax.mail.Address; +import javax.mail.Folder; +import javax.mail.Message; +import javax.mail.internet.InternetAddress; +import java.util.Date; + +/** + * Creates a license whitelist. + * + * @author Sindre Mehus + * @version $Id$ + */ +public class WhitelistGenerator { + + private static final Logger LOG = Logger.getLogger(WhitelistGenerator.class); + + private PaymentDao paymentDao; + + public void generate(Date newerThan) throws Exception { + LOG.info("Starting whitelist update for emails newer than " + newerThan); + + EmailSession session = new EmailSession(); + Folder folder = session.getFolder("[Gmail]/Sent Mail"); + int n = folder.getMessageCount(); + + for (int i = n; i >= 0; i--) { + Message message = folder.getMessage(i); + Date date = message.getSentDate(); + InternetAddress address = (InternetAddress) message.getRecipients(Message.RecipientType.TO)[0]; + String recipient = address.getAddress(); + if (date.before(newerThan)) { + break; + } + LOG.info(date + " " + recipient); + + if (paymentDao.getPaymentByEmail(recipient) == null && !paymentDao.isWhitelisted(recipient)) { + paymentDao.whitelist(recipient); + LOG.info("WHITELISTED " + recipient); + } + } + folder.close(false); + LOG.info("Completed whitelist update."); + } + + public void setPaymentDao(PaymentDao paymentDao) { + this.paymentDao = paymentDao; + } +} |