Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial import of semi-working project.

  • Loading branch information...
commit 50a86b08e9b1c0725c0ef60cee62fbadc69a1c61 0 parents
@mhagander authored
Showing with 1,678 additions and 0 deletions.
  1. +7 −0 .classpath
  2. +2 −0  .gitignore
  3. +33 −0 .project
  4. +5 −0 .settings/org.eclipse.jdt.core.prefs
  5. +25 −0 AndroidManifest.xml
  6. +13 −0 default.properties
  7. BIN  res/drawable-hdpi/icon.png
  8. BIN  res/drawable-ldpi/icon.png
  9. BIN  res/drawable-mdpi/green.png
  10. BIN  res/drawable-mdpi/icon.png
  11. BIN  res/drawable-mdpi/red.png
  12. +8 −0 res/layout/mail_item.xml
  13. +11 −0 res/layout/mailview.xml
  14. +3 −0  res/layout/main.xml
  15. +5 −0 res/layout/main_item.xml
  16. +4 −0 res/values/strings.xml
  17. +296 −0 src/net/hagander/mailinglistmoderator/MailinglistModerator.java
  18. +46 −0 src/net/hagander/mailinglistmoderator/MessageViewActivity.java
  19. +283 −0 src/net/hagander/mailinglistmoderator/QueueListActivity.java
  20. +134 −0 src/net/hagander/mailinglistmoderator/ServerEditor.java
  21. +221 −0 src/net/hagander/mailinglistmoderator/backend/ListServer.java
  22. +52 −0 src/net/hagander/mailinglistmoderator/backend/MailMessage.java
  23. +159 −0 src/net/hagander/mailinglistmoderator/backend/providers/Mailman.java
  24. +174 −0 src/net/hagander/mailinglistmoderator/backend/providers/Majordomo2.java
  25. +48 −0 src/net/hagander/mailinglistmoderator/backend/providers/Unconfigured.java
  26. +58 −0 src/net/hagander/mailinglistmoderator/glue/ListServerAdapter.java
  27. +91 −0 src/net/hagander/mailinglistmoderator/glue/MailMessageAdapter.java
7 .classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="src" path="gen"/>
+ <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
2  .gitignore
@@ -0,0 +1,2 @@
+bin/*
+gen/*
33 .project
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>mailinglistmoderator</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.ApkBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
5 .settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,5 @@
+#Fri Apr 02 19:42:14 CEST 2010
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.source=1.5
25 AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="net.hagander.mailinglistmoderator"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <application android:icon="@drawable/icon" android:label="@string/app_name">
+ <activity android:name=".MailinglistModerator"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".QueueListActivity"
+ android:label="@string/app_name">
+ </activity>
+ <activity android:name=".ServerEditor" android:launchMode="standard"
+ android:label="@string/app_name">
+ </activity>
+<activity android:name=".MessageViewActivity" android:label="@string/app_name"></activity>
+</application>
+
+
+<uses-permission android:name="android.permission.INTERNET"></uses-permission>
+</manifest>
13 default.properties
@@ -0,0 +1,13 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Indicates whether an apk should be generated for each density.
+split.density=false
+# Project target.
+target=android-7
BIN  res/drawable-hdpi/icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  res/drawable-ldpi/icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  res/drawable-mdpi/green.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  res/drawable-mdpi/icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  res/drawable-mdpi/red.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 res/layout/mail_item.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<RelativeLayout android:id="@+id/RelativeLayout02" android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android">
+ <TextView android:text="@+id/TextView_Sender" android:id="@+id/TextView_Sender" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
+ <ImageView android:layout_alignParentRight="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/ImageViewAction"></ImageView>
+ <TextView android:layout_below="@+id/TextView_Sender" android:layout_width="wrap_content" android:id="@+id/TextView_Subject" android:layout_height="wrap_content" android:text="@+id/TextView_Subject" android:maxLines="1" android:textSize="12sp" android:textStyle="italic"></TextView>
+
+</RelativeLayout>
11 res/layout/mailview.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+<TextView android:text="" android:id="@+id/TextView_Body" android:layout_width="fill_parent" android:layout_height="fill_parent"></TextView>
+</LinearLayout>
+</ScrollView>
3  res/layout/main.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<ListView android:id="@+id/ListView01" android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android"></ListView>
5 res/layout/main_item.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<RelativeLayout android:id="@+id/RelativeLayout01" android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android"><TextView android:text="@+id/TextView_ServerName" android:id="@+id/TextView_ServerName" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
+<TextView android:layout_below="@+id/TextView_ServerName" android:layout_width="wrap_content" android:id="@+id/TextView_ServerStatus" android:layout_height="wrap_content" android:text="@+id/TextView_ServerStatus" android:maxLines="1" android:textSize="12sp" android:textStyle="italic"></TextView>
+</RelativeLayout>
4 res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">Mailinglist Moderator</string>
+</resources>
296 src/net/hagander/mailinglistmoderator/MailinglistModerator.java
@@ -0,0 +1,296 @@
+/*
+ * MailinglistModerator.java - This class holds the main activity for the program.
+ *
+ * Copyright (C) 2010 Magnus Hagander <magnus@hagander.net>
+ *
+ * This software is released under the BSD license.
+ */
+package net.hagander.mailinglistmoderator;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Map;
+
+import net.hagander.mailinglistmoderator.backend.ListServer;
+import net.hagander.mailinglistmoderator.glue.ListServerAdapter;
+import android.app.ListActivity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.OnCreateContextMenuListener;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.Toast;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.AdapterView.OnItemClickListener;
+
+/**
+ * @author Magnus Hagander <magnus@hagander.net>
+ *
+ */
+public class MailinglistModerator extends ListActivity {
+ public static ArrayList<ListServer> servers;
+ private ListServerAdapter serverAdapter;
+ private SharedPreferences prefs;
+
+ /* Menu constants */
+ private final int MENU_EDIT_SERVERS = 1;
+ private final int MENU_DELETE_SERVER = 2;
+
+ /* Return codes when calling sub-actions */
+ private final int REQUEST_CODE_EDITSERVERS = 7;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
+
+ servers = new ArrayList<ListServer>();
+ LoadServers();
+
+ serverAdapter = new ListServerAdapter(this, R.layout.main_item, servers);
+ setListAdapter(serverAdapter);
+
+ ListView lv = getListView();
+
+ final MailinglistModerator moderator = this;
+
+ /* Handle server clicks by launching the QueueListActivity */
+ lv.setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView<?> parent, View view,
+ int position, long id) {
+ ListServer s = serverAdapter.getItem(position);
+ if (!s.isPopulated())
+ return;
+
+ /*
+ * Ugly way to pass information to the QueueListActivity we're
+ * going to start - pass it through a static method.
+ */
+ QueueListActivity.setServerInfo(s, moderator);
+ startActivity(new Intent(getApplicationContext(),
+ QueueListActivity.class));
+ }
+ });
+
+ /* Set up the press-and-hold context menu */
+ lv.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ menu.add(Menu.NONE, MENU_DELETE_SERVER, 1, "Delete server");
+ }
+
+ });
+
+ /* Populate list of unmoderated messages in the background */
+ populateServers();
+ }
+
+ /**
+ * Load the list of servers from the application preferences. Doesn't
+ * actually connect and populate information about the server, just loads
+ * the list.
+ *
+ * If there is a server list already, it's cleared and replaced with the new
+ * one.
+ */
+ private void LoadServers() {
+ servers.clear();
+
+ for (Map.Entry<String, ?> entry : prefs.getAll().entrySet()) {
+ // Any property that ends in _listname is considered one of our
+ // servers.
+ if (entry.getKey().endsWith("_listname")) {
+ try {
+ servers
+ .add(ListServer.CreateFromPreference(prefs, entry
+ .getKey().substring(0,
+ entry.getKey().length() - 9)));
+ } catch (Exception ex) {
+ final String msg = ex.toString();
+ /* FIXME: replace with alertbox! */
+ runOnUiThread(new Runnable() {
+ public void run() {
+ Toast.makeText(getApplicationContext(), msg,
+ Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Notify that the list of servers has changed, and do so on the UI thread
+ * to make it safe for calling from anywhere.
+ */
+ public void notifyServersChanged() {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ serverAdapter.notifyDataSetChanged();
+ }
+ });
+ }
+
+ /**
+ * Populate all our servers with information about unmoderated messages, by
+ * connecting to the server and enumerating.
+ *
+ * All checking will be run in a background thread.
+ */
+ private void populateServers() {
+ /*
+ * Update the server list before we get started, since it may take a
+ * while...
+ */
+ notifyServersChanged();
+
+ Runnable r = new Runnable() {
+ public void run() {
+ for (int i = 0; i < servers.size(); i++) {
+ ListServer s = servers.get(i);
+ try {
+ s.Populate();
+ } catch (Exception e) {
+ final String msg = String.format("%s", e);
+ runOnUiThread(new Runnable() {
+ public void run() {
+ Toast.makeText(getApplicationContext(), msg,
+ Toast.LENGTH_LONG).show();
+ }
+ });
+ continue;
+ }
+
+ /*
+ * Since servers are sorted by number of messages, re-sort
+ * the list when it has updated.
+ *
+ * We run this once in each loop so that servers with
+ * messages to moderate on will "bubble up" to the top as we
+ * run.
+ */
+ Collections.sort(servers, new Comparator<ListServer>() {
+ public int compare(ListServer server1,
+ ListServer server2) {
+ if (server1.count() > 0 && server2.count() > 0)
+ // Both servers have items on them, so sort by
+ // name
+ return server1.getName().compareTo(
+ server2.getName());
+ if (server1.count() == 0 && server2.count() == 0)
+ // Neither server has any items, sort by name
+ return server1.getName().compareTo(
+ server2.getName());
+ if (server1.count() == 0)
+ // server1 has nothing, server 2 does, so put
+ // server2 first
+ return 1;
+ return -1;
+ }
+
+ });
+
+ notifyServersChanged();
+ }
+ }
+ };
+ Thread t = new Thread(r, "ServerPopulatingThread");
+ t.start();
+ }
+
+ /**
+ * Create the menu for when the Menu button is pressed.
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, MENU_EDIT_SERVERS, 0, "Servers...");
+ return true;
+ }
+
+ /**
+ * Handle selections in the main menu.
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_EDIT_SERVERS:
+ /*
+ * Edit servers - so launch the ServerEditor activity. We need to
+ * track the result of this activity, so we can reload the list when
+ * it returns.
+ */
+ Intent i = new Intent(getApplicationContext(), ServerEditor.class);
+ startActivityForResult(i, REQUEST_CODE_EDITSERVERS);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Handle selections in the click-and-hold context menu
+ */
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ if (item.getItemId() == MENU_DELETE_SERVER) {
+ /*
+ * FIXME: It's probably a good idea to have a confirmation dialog
+ * here.
+ */
+ AdapterContextMenuInfo menuInfo = (AdapterContextMenuInfo) item
+ .getMenuInfo();
+ String name = serverAdapter.getItem(menuInfo.position).getName();
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.remove(name + "_listname");
+ editor.remove(name + "_baseurl");
+ editor.remove(name + "_password");
+ editor.commit();
+
+ /*
+ * Delete the server from the array, and notify that things have
+ * changed. There is no need to reload the server list again.
+ */
+ servers.remove(serverAdapter.getItem(menuInfo.position));
+ notifyServersChanged();
+ }
+
+ return true;
+ }
+
+ /**
+ * Callback for whenever a sub-activity finishes.
+ */
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_CODE_EDITSERVERS) {
+ /*
+ * The closed activity is our ServerEditor one.
+ *
+ * It will return resultCode=2 if it has added a new server, in
+ * which case we just want to restart the Activity so it'll pick up
+ * the new entry.
+ *
+ * In all other cases, just reload all the servers in case any vital
+ * configuration has changed.
+ */
+ if (resultCode == 2) {
+ Intent i = new Intent(getApplicationContext(),
+ ServerEditor.class);
+ startActivityForResult(i, REQUEST_CODE_EDITSERVERS);
+ } else {
+ LoadServers();
+ populateServers();
+ }
+ }
+ }
+}
46 src/net/hagander/mailinglistmoderator/MessageViewActivity.java
@@ -0,0 +1,46 @@
+/*
+ * MessageViewActivity.java - This class holds the activity to view an individual message
+ *
+ * Copyright (C) 2010 Magnus Hagander <magnus@hagander.net>
+ *
+ * This software is released under the BSD license.
+ */
+package net.hagander.mailinglistmoderator;
+
+import net.hagander.mailinglistmoderator.backend.MailMessage;
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+/**
+ *
+ * @author Magnus Hagander <magnus@hagander.net>
+ *
+ */
+public class MessageViewActivity extends Activity {
+ private MailMessage message;
+
+ // Ugly hack to pass message to the activity when it's started
+ private static MailMessage _passedMessage;
+
+ public static void setMessage(MailMessage message) {
+ _passedMessage = message;
+ }
+
+ public MessageViewActivity() {
+ super();
+ message = _passedMessage;
+ }
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ this.setTitle(message.getSubject());
+
+ setContentView(R.layout.mailview);
+ ((TextView) findViewById(R.id.TextView_Body)).setText(message
+ .getContent());
+ }
+}
283 src/net/hagander/mailinglistmoderator/QueueListActivity.java
@@ -0,0 +1,283 @@
+/*
+ * QueueListActivity.java - This class holds the activity for listing messages on a single server.
+ *
+ * Copyright (C) 2010 Magnus Hagander <magnus@hagander.net>
+ *
+ * This software is released under the BSD license.
+ */
+package net.hagander.mailinglistmoderator;
+
+import net.hagander.mailinglistmoderator.backend.ListServer;
+import net.hagander.mailinglistmoderator.backend.MailMessage;
+import net.hagander.mailinglistmoderator.backend.ListServer.ListServerStatusCallbacks;
+import net.hagander.mailinglistmoderator.backend.MailMessage.statuslevel;
+import net.hagander.mailinglistmoderator.glue.MailMessageAdapter;
+import android.app.AlertDialog;
+import android.app.ListActivity;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.OnCreateContextMenuListener;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.AdapterView.OnItemClickListener;
+
+/**
+ *
+ * @author Magnus Hagander <magnus@hagander.net>
+ *
+ */
+public class QueueListActivity extends ListActivity implements
+ ListServerStatusCallbacks {
+ private ListServer server;
+ private MailinglistModerator parent;
+ private MailMessageAdapter messageAdapter;
+
+ /* Menu constants */
+ private final int MENU_ACCEPT = 1;
+ private final int MENU_REJECT = 2;
+ private final int MENU_DEFER = 3;
+ private final int MENU_APPLY = 4;
+
+ // Ugly hack to pass server and parent information to newly created activity
+ private static ListServer _passedServer;
+ private static MailinglistModerator _passedParent;
+
+ public static void setServerInfo(ListServer server,
+ MailinglistModerator parent) {
+ _passedServer = server;
+ _passedParent = parent;
+ }
+
+ public QueueListActivity() {
+ super();
+ server = _passedServer;
+ parent = _passedParent;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ this.setTitle(String
+ .format("Moderation queue for %s", server.getName()));
+
+ messageAdapter = new MailMessageAdapter(this, R.layout.mail_item,
+ server.getMessages());
+ setListAdapter(messageAdapter);
+
+ ListView lv = getListView();
+
+ /*
+ * Handle clicks on an individual item in the queue by starting the
+ * MessageViewActivity to show the contents of the message.
+ */
+ lv.setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView<?> parent, View view,
+ int position, long id) {
+ MailMessage m = messageAdapter.getItem(position);
+
+ MessageViewActivity.setMessage(m);
+ startActivity(new Intent(getApplicationContext(),
+ MessageViewActivity.class));
+ /*
+ * FIXME Intent i = new Intent();
+ * i.setClassName("net.hagander.mailinglistmoderator",
+ * "net.hagander.mailinglistmoderator.MessageViewActivity");
+ * startActivity(i);
+ */
+ }
+ });
+
+ /*
+ * Set up a press-and-hold context menu for each message
+ */
+ lv.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ menu.add(Menu.NONE, MENU_ACCEPT, 1, "Accept");
+ menu.add(Menu.NONE, MENU_REJECT, 2, "Reject");
+ menu.add(Menu.NONE, MENU_DEFER, 3, "Defer");
+ }
+
+ });
+ }
+
+ private class QueueListActivityThread extends Thread {
+ protected QueueListActivity activity;
+ public QueueListActivityThread(QueueListActivity activity) {
+ super();
+ this.activity = activity;
+ }
+ }
+ /*
+ * Apply any changes that are queued up - that is, any action that's Accept
+ * or Reject, not defer.
+ *
+ * The changes are made by a background thread, but a status dialog is shown
+ * while they run.
+ */
+ private ProgressDialog progressDialog;
+
+ private void applyAllChanges() {
+ if (server.hasChanges()) {
+ progressDialog = new ProgressDialog(this);
+ if (server.doesIndividualModeration()) {
+ /*
+ * If the backend does moderation on an individual message
+ * basis, we enable toe progressbar in the dialog to show how
+ * far along we are.
+ */
+ progressDialog
+ .setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+ }
+ progressDialog.setTitle("Applying...");
+ progressDialog.setMessage("Sending moderation requests...");
+ progressDialog.show();
+
+ new QueueListActivityThread(this) {
+ @Override
+ public void run() {
+ if (server.applyChanges(activity)) {
+ // Reload the moderation queue (for this server only)
+ SetStatusMessage("Reloading moderation queue...");
+ server.Populate();
+
+ // Turn off progress window
+ progressDialog.dismiss();
+
+ if (server.count() == 0)
+ // Drop out to main screen if there are no more
+ // messages
+ finish();
+ else {
+ // Otherwise, refresh our list
+ runOnUiThread(new Runnable() {
+ public void run() {
+ messageAdapter.notifyDataSetChanged();
+ }
+ });
+ /*
+ * Since the count of messages changed, we need to
+ * tell the parent view as well.
+ */
+ parent.notifyServersChanged();
+ }
+ } else {
+ // Changes failed, error message already shown, just get
+ // rid of the dialog.
+ progressDialog.dismiss();
+ }
+ }
+ }.start();
+ }
+ }
+
+ /*
+ * Methods to implement ListServerStatusCallback
+ */
+ public void SetProgressbarPercent(final int percent) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ progressDialog.setProgress(percent);
+ }
+ });
+ }
+
+ public void SetStatusMessage(final String msg) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ progressDialog.setMessage(msg);
+ }
+ });
+ }
+
+ public void ShowError(final String msg) {
+ final QueueListActivity parent = this;
+ runOnUiThread(new Runnable() {
+ public void run() {
+ new AlertDialog.Builder(parent).setCancelable(false)
+ .setMessage(msg).setNegativeButton("OK",
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog,
+ int which) {
+ return;
+ }
+ }).show();
+ }
+ });
+ }
+
+ /* End of ListServerStatusCallback implementation */
+
+ /**
+ * return a "statuslevel" value that corresponds to the menu item that was
+ * chosen.
+ */
+ private statuslevel getStatusLevelFromMenuId(int menuId) {
+ switch (menuId) {
+ case MENU_ACCEPT:
+ return statuslevel.Accept;
+ case MENU_REJECT:
+ return statuslevel.Reject;
+ case MENU_DEFER:
+ return statuslevel.Defer;
+ }
+ return statuslevel.Defer;
+ }
+
+ /**
+ * Handle clicks on the press-and-hold context menu, by simply setting the
+ * message status to the one corresponding to the chosen level.
+ */
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ AdapterContextMenuInfo menuInfo = (AdapterContextMenuInfo) item
+ .getMenuInfo();
+ MailMessage message = messageAdapter.getItem(menuInfo.position);
+ message.setStatus(getStatusLevelFromMenuId(item.getItemId()));
+ messageAdapter.notifyDataSetChanged();
+ return true;
+ }
+
+ /**
+ * Create the main menu.
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, MENU_ACCEPT, 0, "Accept all");
+ menu.add(1, MENU_REJECT, 1, "Reject all");
+ menu.add(2, MENU_DEFER, 2, "Defer all");
+ menu.add(3, MENU_APPLY, 3, "Apply moderation");
+ return true;
+ }
+
+ /**
+ * Handle clicks in the main menu.
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == MENU_ACCEPT || item.getItemId() == MENU_REJECT
+ || item.getItemId() == MENU_DEFER) {
+ statuslevel l = getStatusLevelFromMenuId(item.getItemId());
+
+ for (int i = 0; i < messageAdapter.getCount(); i++) {
+ messageAdapter.getItem(i).setStatus(l);
+ }
+ messageAdapter.notifyDataSetChanged();
+ return true;
+ }
+ if (item.getItemId() == MENU_APPLY) {
+ applyAllChanges();
+ return true;
+ }
+ return false;
+ }
+}
134 src/net/hagander/mailinglistmoderator/ServerEditor.java
@@ -0,0 +1,134 @@
+/*
+ * ServerEditor.java - This class holds the activity for editing preferences.
+ *
+ * Copyright (C) 2010 Magnus Hagander <magnus@hagander.net>
+ *
+ * This software is released under the BSD license.
+ */
+package net.hagander.mailinglistmoderator;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.EditTextPreference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceManager;
+import android.preference.PreferenceScreen;
+import android.text.InputType;
+import android.text.method.PasswordTransformationMethod;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.EditText;
+
+/**
+ * @author Magnus Hagander <magnus@hagander.net>
+ *
+ */
+public class ServerEditor extends PreferenceActivity {
+
+ /* Menu constants */
+ private final int MENU_NEW_SERVER = 1;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setPreferenceScreen(getRootPreferenceScreen());
+ }
+
+ /**
+ * Return the root of the preferences screen that we want to show. The main
+ * thing here is the list of all the servers.
+ */
+ private PreferenceScreen getRootPreferenceScreen() {
+ PreferenceScreen root = getPreferenceManager().createPreferenceScreen(
+ this);
+
+ root.setTitle("Servers");
+ for (int i = 0; i < MailinglistModerator.servers.size(); i++)
+ root.addPreference(getOneServerSet(MailinglistModerator.servers
+ .get(i).getName()));
+ return root;
+ }
+
+ /**
+ * Return a preference screen for one individual server.
+ */
+ private PreferenceScreen getOneServerSet(String name) {
+ PreferenceScreen screen = getPreferenceManager()
+ .createPreferenceScreen(this);
+ screen.setTitle(name);
+
+ /* Create textbox for the base URL */
+ EditTextPreference e_baseurl = new EditTextPreference(this);
+ e_baseurl.setKey(name + "_baseurl");
+ e_baseurl.getEditText().setInputType(InputType.TYPE_TEXT_VARIATION_URI);
+ e_baseurl.setTitle("Base URL");
+ e_baseurl.setDialogTitle("Base URL");
+ screen.addPreference(e_baseurl);
+
+ /* Create textbox for password */
+ EditTextPreference e_password = new EditTextPreference(this);
+ e_password.setKey(name + "_password");
+ e_password.getEditText().setInputType(
+ InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ e_password.getEditText().setTransformationMethod(
+ new PasswordTransformationMethod());
+ e_password.setTitle("Password");
+ e_password.setDialogTitle("Password");
+ screen.addPreference(e_password);
+
+ return screen;
+ }
+
+ /**
+ * Create the main menu.
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, MENU_NEW_SERVER, 0, "New server");
+ return true;
+ }
+
+ /**
+ * Handle clicks in the main menu.
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_NEW_SERVER:
+ final EditText edit = new EditText(this);
+ new AlertDialog.Builder(this).setTitle("New server").setMessage(
+ "Enter list name").setView(edit).setPositiveButton(
+ "Create", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ String name = edit.getText().toString();
+ if (name.length() < 2)
+ return;
+
+ SharedPreferences.Editor editor = PreferenceManager
+ .getDefaultSharedPreferences(
+ getBaseContext()).edit();
+ editor.putString(name + "_listname", name);
+ editor.putString(name + "_baseurl", "");
+ editor.putString(name + "_password", "");
+ editor.commit();
+
+ // Return with resultCode = 2 to indicate we want
+ // the parent to re-launch this Activity
+ setResult(2);
+ finish();
+ }
+ }).setNegativeButton("Cancel",
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ }).show();
+ return true;
+ }
+ return false;
+ }
+}
221 src/net/hagander/mailinglistmoderator/backend/ListServer.java
@@ -0,0 +1,221 @@
+/*
+ * ListServer.java - This class holds an abstract base class for implementing a mailinglist server
+ *
+ * Copyright (C) 2010 Magnus Hagander <magnus@hagander.net>
+ *
+ * This software is released under the BSD license.
+ */
+package net.hagander.mailinglistmoderator.backend;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+
+import net.hagander.mailinglistmoderator.backend.MailMessage.statuslevel;
+import net.hagander.mailinglistmoderator.backend.providers.Mailman;
+import net.hagander.mailinglistmoderator.backend.providers.Majordomo2;
+import net.hagander.mailinglistmoderator.backend.providers.Unconfigured;
+import android.content.SharedPreferences;
+
+/**
+ *
+ * @author Magnus Hagander <magnus@hagander.net>
+ *
+ */
+public abstract class ListServer {
+ protected String listname;
+ protected String rooturl;
+ protected String password;
+
+ protected boolean populated;
+ protected String status;
+ protected ArrayList<MailMessage> messages;
+
+ /**
+ *
+ * @param name
+ * Name of the list
+ * @param rooturl
+ * URL of the root mailinglist server installation (not including
+ * list name)
+ * @param password
+ * Password to access the server (both read and write)
+ */
+ public ListServer(String name, String rooturl, String password) {
+ this.listname = name;
+ this.rooturl = rooturl;
+ this.password = password;
+
+ this.populated = false;
+ this.messages = new ArrayList<MailMessage>();
+ }
+
+ /**
+ * Create a list server instance, by figuring out which type of list it is,
+ * and instantiating the proper class.
+ *
+ * @param name
+ * Name of the list
+ * @param rooturl
+ * Root URL of the server, not including the list name
+ * @param password
+ * Password to access the server
+ * @return A ListServer instance representing this server.
+ */
+ public static ListServer Create(String name, String rooturl, String password) {
+ if (rooturl.contains("/admindb"))
+ return new Mailman(name, rooturl, password);
+ if (rooturl.contains("mj_wwwadm"))
+ return new Majordomo2(name, rooturl, password);
+ return new Unconfigured(name, rooturl, password);
+ }
+
+ /**
+ * Create a ListServer instance by reading the application preferences for
+ * it.
+ *
+ * @param pref
+ * Instance of SharedPreferences to use.
+ * @param name
+ * Name of the list.
+ * @return A ListServer instance representing this server.
+ */
+ public static ListServer CreateFromPreference(SharedPreferences pref,
+ String name) {
+ String baseurl = pref.getString(name + "_baseurl", "");
+ String password = pref.getString(name + "_password", "");
+
+ return Create(name, baseurl, password);
+ }
+
+ /*
+ * Abstract methods, do be implemented by child classes.
+ */
+ protected abstract ArrayList<MailMessage> EnumerateMessages();
+
+ public abstract boolean doesIndividualModeration();
+
+ public abstract boolean applyChanges(ListServerStatusCallbacks callbacks);
+
+ /**
+ * Return the name of the list.
+ *
+ * @return the name of the list
+ */
+ public String getName() {
+ return listname;
+ }
+
+ /**
+ * Get the current status for this list (loading, number of messages etc).
+ *
+ * @return a string representing the current status for this list
+ */
+ public String getStatus() {
+ if (populated)
+ return status;
+ else
+ return "loading...";
+ }
+
+ /**
+ * Check if this has been queried and populated with a list of mails to
+ * moderate.
+ *
+ * @return if the list is populated.
+ */
+ public boolean isPopulated() {
+ return populated;
+ }
+
+ /**
+ * Get all messages in the moderation queue on the list.
+ *
+ * @return a list of all messages on this list
+ */
+ public ArrayList<MailMessage> getMessages() {
+ return messages;
+ }
+
+ /**
+ * Populate the list with messages by querying the server.
+ *
+ * Also sets the local status string.
+ */
+ public void Populate() {
+ messages.clear();
+ messages.addAll(EnumerateMessages());
+
+ populated = true;
+ status = String.format("%d unmoderated messages", messages.size());
+ }
+
+ /**
+ * Get the number of messages in the queue.
+ *
+ * @return number of messages
+ */
+ public int count() {
+ return messages.size();
+ }
+
+ /**
+ * Check if the queue has any changes to apply (any messages marked as
+ * Accept or Reject, not Defer)
+ *
+ * @return if there are any queued changes.
+ */
+ public boolean hasChanges() {
+ for (int i = 0; i < messages.size(); i++)
+ if (messages.get(i).getStatus() != statuslevel.Defer)
+ return true;
+ return false;
+ }
+
+ /**
+ * Interface listing callbacks from moderation operations that may take
+ * time.
+ */
+ public interface ListServerStatusCallbacks {
+ public void SetStatusMessage(String msg);
+
+ public void SetProgressbarPercent(int percent);
+
+ public void ShowError(String msg);
+ }
+
+ /*
+ * Utility functions for implementations to call.
+ */
+
+ /**
+ * Connect and fetch an URL, returning a string with the contents of the
+ * URL.
+ */
+ protected String FetchUrl(String url) {
+ try {
+ URL u = new URL(url);
+ URLConnection c = u.openConnection();
+ InputStreamReader isr = new InputStreamReader(c.getInputStream());
+ BufferedReader r = new BufferedReader(isr);
+ StringWriter sw = new StringWriter();
+ String line;
+ while ((line = r.readLine()) != null) {
+ sw.write(line);
+ sw.write("\n");
+ }
+ return sw.toString();
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(String.format(
+ "Failed to fetch url %s: %s", url, e));
+ } catch (IOException e) {
+ throw new RuntimeException(String.format(
+ "Failed to fetch url %s: %s", url, e));
+ }
+ }
+}
52 src/net/hagander/mailinglistmoderator/backend/MailMessage.java
@@ -0,0 +1,52 @@
+/*
+ * MailMessage.java - This class holds an abstract base class for implementing a mailinglist-specific
+ * message with required identifiers and content.
+ *
+ * Copyright (C) 2010 Magnus Hagander <magnus@hagander.net>
+ *
+ * This software is released under the BSD license.
+ */
+package net.hagander.mailinglistmoderator.backend;
+
+/**
+ *
+ * @author Magnus Hagander <magnus@hagander.net>
+ *
+ * This base class is simply a container for some common properties.
+ */
+public abstract class MailMessage {
+ private String sender;
+ private String subject;
+ private String content;
+ private statuslevel status = statuslevel.Defer;
+
+ public enum statuslevel {
+ Accept, Reject, Defer
+ };
+
+ public MailMessage(String sender, String subject, String content) {
+ this.sender = sender;
+ this.subject = subject;
+ this.content = content;
+ }
+
+ public String getSender() {
+ return sender;
+ }
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public void setStatus(statuslevel status) {
+ this.status = status;
+ }
+
+ public statuslevel getStatus() {
+ return status;
+ }
+}
159 src/net/hagander/mailinglistmoderator/backend/providers/Mailman.java
@@ -0,0 +1,159 @@
+/*
+ * Mailman.java - This class holds implements mailinglist management for GNU Mailman lists.
+ *
+ * Copyright (C) 2010 Magnus Hagander <magnus@hagander.net>
+ *
+ * This software is released under the BSD license.
+ */
+package net.hagander.mailinglistmoderator.backend.providers;
+
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import net.hagander.mailinglistmoderator.backend.ListServer;
+import net.hagander.mailinglistmoderator.backend.MailMessage;
+import net.hagander.mailinglistmoderator.backend.MailMessage.statuslevel;
+
+/**
+ *
+ * @author Magnus Hagander <magnus@hagander.net>
+ *
+ */
+public class Mailman extends ListServer {
+ public Mailman(String name, String rooturl, String password) {
+ super(name, rooturl, password);
+ }
+
+ /*
+ * Regular expressions for matching message lists and contents.
+ */
+ private static final Pattern enumMailPattern = Pattern
+ .compile(
+ "<table CELLPADDING=\"0\" WIDTH=\"100%\" CELLSPACING=\"0\">(.*?)</table>\\s+<p>",
+ Pattern.DOTALL);
+ private static final Pattern messageContentPattern = Pattern
+ .compile(
+ "<td ALIGN=\"right\"><strong>From:</strong></td>\\s+<td>([^<]+)</td>.*?<td ALIGN=\"right\"><strong>Subject:</strong></td>\\s+<td>([^<]+)</td>.*?<td><TEXTAREA NAME=fulltext-(\\d+) ROWS=10 COLS=76 WRAP=soft READONLY>([^<]+)</TEXTAREA></td>",
+ Pattern.DOTALL);
+
+ /**
+ * Enumerate all messages on the list, and return them as an ArrayList.
+ */
+ @Override
+ protected ArrayList<MailMessage> EnumerateMessages() {
+ ArrayList<MailMessage> messages = new ArrayList<MailMessage>();
+
+ // Fetch the details=all page which contains everything we need.
+ String page = FetchUrl(String.format("%s/%s/?details=all&adminpw=%s",
+ rooturl, listname, password));
+
+ /*
+ * Attempt to locate all the messages in the queue.
+ */
+ Matcher m = enumMailPattern.matcher(page);
+ while (m.find()) {
+ Matcher sm = messageContentPattern.matcher(m.group(1));
+ if (sm.find()) {
+ // Got a message
+ // group(1) == from
+ // group(2) == subject
+ // group(3) == id
+ // group(4) == contents
+ messages.add(new MailmanMessage(Integer.parseInt(sm.group(3)),
+ sm.group(1), sm.group(2), sm.group(4)));
+ }
+ }
+ return messages;
+ }
+
+ /**
+ * In mailman we moderate a whole batch of messages in a single call.
+ */
+ @Override
+ public boolean doesIndividualModeration() {
+ return false;
+ }
+
+ /**
+ * Apply any queued moderations to this list.
+ */
+ @Override
+ public boolean applyChanges(ListServerStatusCallbacks callbacks) {
+ // The whole moderation operation will go in a single querystring
+ StringBuilder str = new StringBuilder();
+ str.append(rooturl);
+ str.append("/");
+ str.append(listname);
+ str.append("/?");
+
+ // Collect a querystring with all the message ids we are moderating and
+ // what to do with them.
+ int count = 0;
+ for (int i = 0; i < messages.size(); i++) {
+ MailmanMessage msg = (MailmanMessage) messages.get(i);
+ if (msg.getStatus() != statuslevel.Defer) {
+ str.append(String.format("%d=%d&", msg.id, msg
+ .getStatusPostCode()));
+ count++;
+ }
+ }
+
+ if (count == 0)
+ /*
+ * Should never happen, but just in case, so we don't construct a
+ * bad URL
+ */
+ return false;
+
+ callbacks.SetStatusMessage(String.format("Moderating %d messages...",
+ count));
+
+ // Append our password to the request
+ str.append("adminpw=");
+ str.append(password);
+
+ /*
+ * Unfortunately mailman doesn't actually tell us if our modifications
+ * succeeded or not. We'll get an exception if the call failed, of
+ * course, but not if we passed invalid data.
+ */
+ try {
+ FetchUrl(str.toString());
+ } catch (Exception ex) {
+ callbacks.ShowError(ex.toString());
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Implementation of MailMessage holding additional mailman-specific
+ * properties.
+ */
+ private class MailmanMessage extends MailMessage {
+ private int id;
+
+ public MailmanMessage(int id, String sender, String subject,
+ String content) {
+ super(sender, subject, content);
+ this.id = id;
+ }
+
+ /**
+ * Map the status code to the POST value in a mailman form.
+ */
+ public int getStatusPostCode() {
+ switch (getStatus()) {
+ case Accept:
+ return 1;
+ case Reject:
+ return 3; // map reject to discard, consider supporting both in
+ // the future
+ }
+ return 0; // default to defer if we're called with something
+ // unexpected.
+ }
+ }
+}
174 src/net/hagander/mailinglistmoderator/backend/providers/Majordomo2.java
@@ -0,0 +1,174 @@
+/*
+ * Majordomo2.java - This class holds implements mailinglist management for Majordomo2 lists.
+ *
+ * Copyright (C) 2010 Magnus Hagander <magnus@hagander.net>
+ *
+ * This software is released under the BSD license.
+ */
+package net.hagander.mailinglistmoderator.backend.providers;
+
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import net.hagander.mailinglistmoderator.backend.ListServer;
+import net.hagander.mailinglistmoderator.backend.MailMessage;
+import net.hagander.mailinglistmoderator.backend.MailMessage.statuslevel;
+
+/**
+ *
+ * @author Magnus Hagander <magnus@hagander.net>
+ *
+ */
+public class Majordomo2 extends ListServer {
+ public Majordomo2(String name, String rooturl, String password) {
+ super(name, rooturl, password);
+ }
+
+ /*
+ * Regular expressions for matching message lists and contents.
+ */
+
+ private static final Pattern enumMailPattern = Pattern
+ .compile(
+ "<td><input type=\"checkbox\" name=\"extra\"\\s+value=\"([^\"]+)\">",
+ Pattern.DOTALL);
+ private static final Pattern mailDetailsPattern = Pattern
+ .compile(
+ "<tr><td>From\\s+</td><td>([^<]+)</td>.*?<tr><td>Subject\\s+</td><td>([^<]+)</td>.*?<pre>\\s+([^<]+)\\s*</pre>",
+ Pattern.DOTALL);
+
+ /**
+ * Enumerate all messages on the list, and return them as an ArrayList.
+ */
+ @Override
+ protected ArrayList<MailMessage> EnumerateMessages() {
+ ArrayList<MailMessage> messages = new ArrayList<MailMessage>();
+
+ // Fetcha list of all the tokens in "consult" mode
+ String page = FetchUrl(String.format(
+ "%s?passw=%s&list=%s&func=showtokens-consult", rooturl,
+ password, listname));
+
+ Matcher m = enumMailPattern.matcher(page);
+ while (m.find()) {
+ /*
+ * Majordomo2, in it's infinite wisdom, doesn't include the subject
+ * line on the list. So we need to fetch the actual message contents
+ * once for each to get it.
+ */
+ String url = String.format(
+ "%s?passw=%s&list=%s&func=tokeninfo&extra=%s", rooturl,
+ password, listname, m.group(1));
+ String subpage = FetchUrl(url);
+ if (subpage == null) {
+ /*
+ * No tokeinfo returned here. Just ignore this message - maybe
+ * somebody moderated it while we were looking at others.
+ */
+ continue;
+ }
+ Matcher sm = mailDetailsPattern.matcher(subpage);
+ if (sm.find()) {
+ messages.add(new Majordomo2Message(m.group(1), sm.group(1), sm
+ .group(2), sm.group(3)));
+ }
+
+ }
+ return messages;
+ }
+
+ /**
+ * Extremely trivial implementation of decoding some HTML escapes for nicer
+ * viewing.
+ */
+ private static String trivialDecode(String s) {
+ return s.replaceAll("&quot;", "\"").replaceAll("&lt;", "<").replaceAll(
+ "&gt;", ">");
+ }
+
+ /**
+ * In majordomo2 we moderate each message individually.
+ */
+ @Override
+ public boolean doesIndividualModeration() {
+ return true;
+ }
+
+ /**
+ * Apply any moderations to this list.
+ */
+ @Override
+ public boolean applyChanges(ListServerStatusCallbacks callbacks) {
+ /*
+ * Collect all the messages we're actually going to do moderation on in
+ * it's own list.
+ */
+ ArrayList<Majordomo2Message> msglist = new ArrayList<Majordomo2Message>();
+
+ for (int i = 0; i < messages.size(); i++) {
+ Majordomo2Message msg = (Majordomo2Message) messages.get(i);
+ if (msg.getStatus() != statuslevel.Defer) {
+ msglist.add(msg);
+ }
+ }
+ if (msglist.size() == 0)
+ /*
+ * Should never happen, but just in case, so we don't construct a
+ * bad URL
+ */
+ return false;
+
+ /*
+ * Now that we know how many, moderate each individual one.
+ */
+ for (int i = 0; i < msglist.size(); i++) {
+ callbacks.SetStatusMessage(String.format(
+ "Moderating message %d of %d", i, msglist.size()));
+ callbacks.SetProgressbarPercent(i * 100 / msglist.size());
+
+ try {
+ FetchUrl(String.format("%s?passw=%s&list=%s&func=%s&extra=%s",
+ rooturl, password, listname, msglist.get(i)
+ .getMajordomoFunc(), msglist.get(i).token));
+ } catch (Exception ex) {
+ callbacks.ShowError(ex.toString());
+ return false;
+ }
+ }
+
+ // Make sure we exit with a full progressbar.
+ callbacks.SetProgressbarPercent(100);
+
+ return true;
+ }
+
+ /**
+ * Implementation of MailMessage holding additional majordomo2-specific
+ * properties.
+ */
+ private class Majordomo2Message extends MailMessage {
+ private String token;
+
+ public Majordomo2Message(String token, String sender, String subject,
+ String content) {
+ super(trivialDecode(sender), trivialDecode(subject), content);
+ this.token = token;
+ }
+
+ /**
+ * Map the status code to the text representation to be used on a
+ * Majordomo2 form
+ */
+ public String getMajordomoFunc() {
+ switch (getStatus()) {
+ case Accept:
+ return "accept";
+ case Reject:
+ return "reject-quiet"; // configurable in the future to allow
+ // non-quiet?
+ }
+ return "thisisnotafunctionandshouldneverbecalled";
+ }
+ }
+}
48 src/net/hagander/mailinglistmoderator/backend/providers/Unconfigured.java
@@ -0,0 +1,48 @@
+/*
+ * Unconfirued.java - This class holds implements a placeholder mailinglist manager for a list
+ * that hasn't been configured yet and as such can't have it's type
+ * identified.
+ *
+ * Copyright (C) 2010 Magnus Hagander <magnus@hagander.net>
+ *
+ * This software is released under the BSD license.
+ */
+package net.hagander.mailinglistmoderator.backend.providers;
+
+import java.util.ArrayList;
+
+import net.hagander.mailinglistmoderator.backend.ListServer;
+import net.hagander.mailinglistmoderator.backend.MailMessage;
+
+/**
+ *
+ * @author Magnus Hagander <magnus@hagander.net>
+ *
+ */
+public class Unconfigured extends ListServer {
+
+ public Unconfigured(String name, String rooturl, String password) {
+ super(name, rooturl, password);
+ }
+
+ @Override
+ protected ArrayList<MailMessage> EnumerateMessages() {
+ return new ArrayList<MailMessage>();
+ }
+
+ @Override
+ public boolean applyChanges(ListServerStatusCallbacks callbacks) {
+ return false;
+ }
+
+ @Override
+ public boolean doesIndividualModeration() {
+ return false;
+ }
+
+ @Override
+ public void Populate() {
+ populated = true;
+ status = String.format("Unconfigured list");
+ }
+}
58 src/net/hagander/mailinglistmoderator/glue/ListServerAdapter.java
@@ -0,0 +1,58 @@
+/*
+ * ListServerAdapter.java - This class holds an ArrayAdapter for the main activity.
+ *
+ * Copyright (C) 2010 Magnus Hagander <magnus@hagander.net>
+ *
+ * This software is released under the BSD license.
+ */
+package net.hagander.mailinglistmoderator.glue;
+
+import java.util.ArrayList;
+
+import net.hagander.mailinglistmoderator.R;
+import net.hagander.mailinglistmoderator.backend.ListServer;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+/**
+ * @author Magnus Hagander <magnus@hagander.net>
+ *
+ */
+public class ListServerAdapter extends ArrayAdapter<ListServer> {
+ private ArrayList<ListServer> items;
+
+ public ListServerAdapter(Context context, int textViewResourceId,
+ ArrayList<ListServer> objects) {
+ super(context, textViewResourceId, objects);
+
+ items = objects;
+ }
+
+ /**
+ * Create a view that contains the information we want to show about each
+ * list.
+ */
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View v = convertView;
+
+ if (v == null) {
+ LayoutInflater vi = (LayoutInflater) getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ v = vi.inflate(R.layout.main_item, null);
+ }
+ ListServer o = items.get(position);
+ if (o != null) {
+ TextView n = (TextView) v.findViewById(R.id.TextView_ServerName);
+ TextView s = (TextView) v.findViewById(R.id.TextView_ServerStatus);
+
+ n.setText(o.getName());
+ s.setText(o.getStatus());
+ }
+ return v;
+ }
+}
91 src/net/hagander/mailinglistmoderator/glue/MailMessageAdapter.java
@@ -0,0 +1,91 @@
+/*
+ * MailMessageAdapter.java - This class holds an ArrayAdapter for the QueueListActivity activity.
+ *
+ * Copyright (C) 2010 Magnus Hagander <magnus@hagander.net>
+ *
+ * This software is released under the BSD license.
+ */
+package net.hagander.mailinglistmoderator.glue;
+
+import java.util.ArrayList;
+
+import net.hagander.mailinglistmoderator.R;
+import net.hagander.mailinglistmoderator.backend.MailMessage;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ *
+ * @author Magnus Hagander <magnus@hagander.net>
+ *
+ */
+public class MailMessageAdapter extends ArrayAdapter<MailMessage> {
+ private ArrayList<MailMessage> items;
+
+ private Bitmap img_green = null, img_red = null;
+
+ public MailMessageAdapter(Context context, int textViewResourceId,
+ ArrayList<MailMessage> objects) {
+ super(context, textViewResourceId, objects);
+
+ items = objects;
+
+ try {
+ img_green = BitmapFactory.decodeResource(context.getResources(),
+ R.drawable.green);
+ img_red = BitmapFactory.decodeResource(context.getResources(),
+ R.drawable.red);
+ } catch (Exception e) {
+ Log.w("MailMessageAdapter", String.format(
+ "Exception loading images: %s", e.getMessage()));
+ }
+ }
+
+ /**
+ * Create a view that contains the information we want to show about each
+ * message, including an image representing the status.
+ */
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View v = convertView;
+ if (v == null) {
+ LayoutInflater vi = (LayoutInflater) getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ v = vi.inflate(R.layout.mail_item, null);
+ }
+ MailMessage o = items.get(position);
+ if (o != null) {
+ TextView sender = (TextView) v.findViewById(R.id.TextView_Sender);
+ TextView subj = (TextView) v.findViewById(R.id.TextView_Subject);
+ ImageView i = (ImageView) v.findViewById(R.id.ImageViewAction);
+
+ sender.setText(o.getSender());
+ subj.setText(o.getSubject());
+ try {
+ switch (o.getStatus()) {
+ case Accept:
+ i.setImageBitmap(img_green);
+ break;
+ case Reject:
+ i.setImageBitmap(img_red);
+ break;
+ case Defer:
+ i.setImageBitmap(null);
+ break;
+ }
+ } catch (Exception e) {
+ Log.w("getView", String.format("Meh, got exception: %s", e
+ .getMessage()));
+ }
+ }
+ return v;
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.