com.almarsoft.GroundhogReader.lib.ServerManager.java Source code

Java tutorial

Introduction

Here is the source code for com.almarsoft.GroundhogReader.lib.ServerManager.java

Source

/*
Groundhog Usenet Reader
Copyright (C) 2008-2010  Juan Jose Alvarez Martinez
    
This program 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 2
of the License, or (at your option) any later version.
    
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package com.almarsoft.GroundhogReader.lib;

import com.almarsoft.GroundhogReader.lib.TrustManagerFactory;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.net.SocketException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HashSet;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;

import org.apache.commons.net.io.DotTerminatedMessageReader;
import org.apache.commons.net.nntp.Article;
import org.apache.commons.net.nntp.NewsgroupInfo;
import org.apache.james.mime4j.message.Header;
import org.apache.james.mime4j.message.Message;
import org.apache.james.mime4j.parser.Field;
import org.apache.james.mime4j.parser.MimeEntityConfig;
import org.apache.james.mime4j.storage.DefaultStorageProvider;
import org.apache.james.mime4j.storage.TempFileStorageProvider;
import org.apache.james.mime4j.storage.ThresholdStorageProvider;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.sqlite.SQLiteDatabase;
import android.preference.PreferenceManager;
import android.util.Log;

final public class ServerManager {

    private String mGroup;
    private int mGroupID;

    private NNTPExtended mClient = null;
    private NewsgroupInfo mGroupInfo;
    private Context mContext;
    private HashSet<String> mBannedThreadsSet;
    private HashSet<String> mBannedTrollsSet;
    private boolean mFetchLatest = false;

    public ServerManager(Context callerContext) {

        mContext = callerContext;
        // XXX: Ver si esto se puede hacer fuera de aqui o pasarselo... solo se usa en un sitio
        mBannedTrollsSet = DBUtils.getBannedTrolls(mContext);
    }

    // Destructor
    protected void finalize() throws Throwable {
        stop();
    }

    public void stop() {

        if (mClient != null && mClient.isConnected()) {
            try {
                mClient.disconnect();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        mClient = null;
    }

    // Connect to the server configured in the settings. Please note that mClient.isConnected
    // works when it says that you are not connected but NOT when is returns "you're connected", for
    // example after a phone sleep it will usually say true while in fact the socket died. That's the 
    // reason of the ugly try { } catch() { reconnect_and_try_again }
    public void clientConnectIfNot() throws IOException, ServerAuthException {

        if (mClient == null || !mClient.isConnected()) {
            connect();
        }
    }

    private void connect() throws SocketException, IOException, ServerAuthException {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);

        String tmpPort = prefs.getString("port", "119");

        if (tmpPort != null)
            tmpPort = tmpPort.trim();

        if (tmpPort == null || tmpPort.length() == 0) {
            // Fix for migrating f*cked config files (from a bug in a previous version)
            tmpPort = "119";
            Editor edit = prefs.edit();
            edit.putString("port", tmpPort);
            edit.commit();
        }

        int port = new Integer(tmpPort);
        boolean needsAuth = prefs.getBoolean("needsAuth", false);

        String host = prefs.getString("host", null);
        if (host != null)
            host = host.trim();

        String clogin = prefs.getString("login", null);
        if (clogin != null)
            clogin = clogin.trim();

        String cpass = prefs.getString("pass", null);
        if (cpass != null)
            cpass = cpass.trim();

        boolean useSSL = prefs.getBoolean("useSSL", false);
        boolean trustAllCertificates = prefs.getBoolean("trustAllCertificates", false);

        mClient = new NNTPExtended();

        if (useSSL) {
            Log.i("CgroundhogReader.lib.ServerManager", "Using SSL...");
            try {
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, new TrustManager[] { TrustManagerFactory.get(host, !trustAllCertificates) },
                        new SecureRandom());
                mClient.setSocketFactory(sslContext.getSocketFactory());
            } catch (KeyManagementException e) {
                Log.e("GroundhogReader.lib.ServerManager", "Caught KeyManagementException", e);
            } catch (NoSuchAlgorithmException e) {
                Log.e("GroundhogReader.lib.ServerManager", "Caught NoSuchAlgorihmException", e);
            }
        }

        // Get the configuration host, port, username and pass
        mClient.connect(host, port);

        if (needsAuth) {
            if (!mClient.authenticate(clogin, cpass)) {
                throw new ServerAuthException();
            }
        }
    }

    public void selectNewsGroupWithoutConnect(String group) {
        mGroup = group;
    }

    public boolean selectNewsGroupConnecting(String group) throws IOException, ServerAuthException {
        mGroup = group;
        mGroupID = DBUtils.getGroupIdFromName(group, mContext);
        mBannedThreadsSet = DBUtils.getBannedThreads(group, mContext);

        clientConnectIfNot();

        mGroupInfo = new NewsgroupInfo();

        try {
            return mClient.selectNewsgroup(mGroup, mGroupInfo);
        } catch (IOException e) {
            connect();
            return mClient.selectNewsgroup(mGroup, mGroupInfo);
        }
    }

    /**
     * Catchup with the server, so only messages from now on will be downloaded 
     * 
     */
    public void catchupGroup(String group) throws IOException, ServerAuthException {

        if (mGroup == null || mGroup.compareTo(group) != 0 || mGroupInfo == null
                || mGroupInfo.getNewsgroup().compareTo(group) != 0) {
            selectNewsGroupConnecting(group);
        }
        DBUtils.storeGroupLastFetchedMessageNumber(group, mGroupInfo.getLastArticle(), mContext);
    }

    // XXX: Ver las llamadas que usan selectNewsGroup para ver si comprueban el valor devuelto
    // XXX: mBannedThreadSet, seria conveniente que se leyera externamente y se le pasara como argumento
    public boolean selectNewsGroup(String group, boolean offlineMode) throws ServerAuthException, IOException {

        mGroup = group;

        if (offlineMode) {
            return true;
        }

        clientConnectIfNot();

        if (group == mGroup && mClient.isConnected()) {
            return true;
        }

        return selectNewsGroupConnecting(group);
    }

    // =====================================================================================
    // Retrieve the list of article numbers from the server and return a vector
    // of articles
    // considering the user limit
    // =====================================================================================

    public Vector<Long> selectGroupAndgetArticleNumbersReverse(String group, long lastFetched, int limit)
            throws IOException, ServerAuthException, UsenetReaderException {
        clientConnectIfNot();

        // This also loads the mGroupInfo class
        if (!selectNewsGroupConnecting(group)) {
            throw new UsenetReaderException("Could not select group " + group);
        }

        lastFetched++;
        long lastMessage = mGroupInfo.getLastArticle();
        long firstMessage = 0;

        if ((lastMessage - limit) > lastFetched) {
            firstMessage = lastMessage - limit;
        } else {
            firstMessage = lastFetched;
        }

        long diff = lastMessage - (firstMessage - 1);
        if (diff < 0)
            return null;
        Vector<Long> msgNumbers = new Vector<Long>((int) diff);
        for (long i = firstMessage; i <= lastMessage; i++) {
            msgNumbers.add(i);
        }

        return msgNumbers;
    }

    public Vector<Long> selectGroupAndGetArticleNumbers(String group, long firstMsg, int limit)
            throws IOException, ServerAuthException, UsenetReaderException {
        clientConnectIfNot();

        // This also loads the mGroupInfo class
        if (!selectNewsGroupConnecting(group)) {
            throw new UsenetReaderException("Could not select group " + group);
        }

        if (firstMsg == -1) {
            // -1 = flag marking the first time in the group, get the last messages
            return selectGroupAndgetArticleNumbersReverse(group, 1, limit);
        }

        long lastMessage = 0;
        if (mGroupInfo.getLastArticle() < firstMsg + limit) {
            lastMessage = mGroupInfo.getLastArticle();
        } else {
            lastMessage = firstMsg + limit;
        }

        long diff = lastMessage - (firstMsg - 1);
        if (diff < 0)
            return null;
        Vector<Long> msgNumbers = new Vector<Long>((int) diff);

        for (long i = firstMsg; i <= lastMessage; i++) {
            msgNumbers.add(i);
        }

        return msgNumbers;
    }

    // ===============================================================================
    // Retrieve and article by number from the server and store it into the
    // database. Return the msgId of the article. Can accept an already created SQLiteDatabase
    // object to avoid too many object/database open/database close inside loop (or
    // can be null to let the DBUtils create it every time.)
    // ===============================================================================

    public Vector<Object> getArticleInfoFromXOVER(long articleNumber, String charset, SQLiteDatabase catchedDB)
            throws IOException, UsenetReaderException, ServerAuthException {
        clientConnectIfNot();

        long ddbbId = -1;
        Vector<Object> ret = null;

        // Get the article information (shorter than the header; we'll fetch the body and the
        // body when the user clicks on an article.)
        Article articleInfo = buildArticleInfoFromXOVER(articleNumber);

        if (articleInfo != null) {
            String from = articleInfo.getFrom();
            if ((!mBannedThreadsSet.contains(articleInfo.simplifiedSubject()))
                    && (!mBannedTrollsSet.contains(from))) {

                ddbbId = insertArticleInDB(articleInfo, articleNumber, from, charset, catchedDB);
            }

            ret = new Vector<Object>(2);
            ret.add(ddbbId);
            ret.add(articleInfo.getArticleId());
        }

        return ret;
    }

    // =======================================================================================================
    // This is the same as getAndInsertArticleInfo, but instead of using the (small) data from XOVER uses the 
    // complete header. This is used in offline mode so we don't have to do an XOVER and then a HEADER, which 
    // is redundant
    // =======================================================================================================
    public Vector<Object> getArticleInfoAndHeaderFromHEAD(long serverNumber, String charset,
            SQLiteDatabase catchedDB, String group) throws IOException, UsenetReaderException, ServerAuthException {
        clientConnectIfNot();

        long ddbbId = -1;
        Vector<Object> ret = null;
        Reader reader = null;

        try {
            reader = (DotTerminatedMessageReader) mClient.retrieveArticleHeader(serverNumber);
        } catch (IOException e) {
            connect();
            reader = (DotTerminatedMessageReader) mClient.retrieveArticleHeader(serverNumber);
        }

        if (reader != null) {
            Header header = new Header(new ReaderInputStream(reader));
            Article article = new Article();
            article.setArticleNumber(serverNumber);
            article.setSubject(header.getField("Subject").getBody().trim());
            article.setFrom(header.getField("From").getBody().trim());
            article.setDate(header.getField("Date").getBody().trim());
            article.setArticleId(header.getField("Message-ID").getBody().trim());

            // Take the references
            Field refsField = header.getField("References");
            String refsStr = null;

            if (refsField != null)
                refsStr = header.getField("References").getBody().trim();

            if (refsStr != null) {
                String[] refs = refsStr.split(" ");

                for (String r : refs) {
                    if (r.trim().length() == 0)
                        continue;
                    article.addReference(r.trim());
                }
            }

            if (article != null) {
                String from = article.getFrom();
                if ((!mBannedThreadsSet.contains(article.simplifiedSubject()))
                        && (!mBannedTrollsSet.contains(from))) {

                    ddbbId = insertArticleInDB(article, serverNumber, from, charset, catchedDB);
                }
            }

            // Meter la cabecera en la BBDD
            String outputPath = UsenetConstants.EXTERNALSTORAGE + "/" + UsenetConstants.APPNAME
                    + "/offlinecache/groups/" + group + "/header/";
            FSUtils.writeStringToDiskFile(header.toString(), outputPath, Long.toString(ddbbId));
            DBUtils.setMessageCatched(ddbbId, true, mContext);

            ret = new Vector<Object>(2);
            ret.add(ddbbId);
            ret.add(article.getArticleId());
        }

        return ret;
    }

    private Article buildArticleInfoFromXOVER(long articleNumber) throws IOException, ServerAuthException {
        clientConnectIfNot();

        Article article = null;
        Reader reader = null;

        try {
            reader = (DotTerminatedMessageReader) mClient.retrieveArticleInfo(articleNumber);
        } catch (IOException e) {
            connect();
            reader = (DotTerminatedMessageReader) mClient.retrieveArticleInfo(articleNumber);
        }

        if (reader != null) {
            String theInfo = MessageTextProcessor.readerToString(reader);

            if (theInfo.trim().length() == 0) {
                return null;
            }

            StringTokenizer st = new StringTokenizer(theInfo, "\n");
            String refsStr = null;

            try {
                // Extract the article information
                // Mandatory format (from NNTP RFC 2980) is :
                // Subject\tAuthor\tDate\tID\tReference(s)\tByte Count\tLine Count

                StringTokenizer stt = new StringTokenizer(st.nextToken(), "\t");
                article = new Article();
                article.setArticleNumber(Long.parseLong(stt.nextToken()));
                article.setSubject(stt.nextToken());
                article.setFrom(stt.nextToken());
                article.setDate(stt.nextToken());
                article.setArticleId(stt.nextToken());

                refsStr = stt.nextToken();
            } catch (NoSuchElementException e) {
                Log.w(UsenetConstants.APPNAME,
                        "NoSuchElementException parsing the article info, malformed or interrupted message?: "
                                + theInfo);
                return null;
            }

            catch (NumberFormatException e) {
                Log.e(UsenetConstants.APPNAME,
                        "NumberFormatException in getArticleInfo: " + theInfo + " " + e.toString());
            }

            // Crappy heuristics... but nextToken skips the empty reference if it's no reference and give up the 
            // next token as reference
            if (refsStr != null && refsStr.contains("@")) {
                String[] refs = refsStr.split(" ");

                for (String r : refs)
                    article.addReference(r);
            }
        }

        return article;
    }

    // ======================================================================
    // Decode, process and insert the articleInfo into the DB, return the _id
    // ======================================================================

    private long insertArticleInDB(Article articleInfo, long articleNumber, String decodedfrom, String charset,
            SQLiteDatabase catchedDB) throws UsenetReaderException {

        // Get the reference list as a string instead of as an array of strings
        // for insertion into the DB
        String[] references = articleInfo.getReferences();
        StringBuilder references_buff = new StringBuilder();
        references_buff.append("");
        int referencesLen = references.length;

        for (int i = 0; i < referencesLen; i++) {
            if (i == (referencesLen - 1)) {
                references_buff.append(references[i]);
            } else {
                references_buff.append(references[i]);
                references_buff.append(" ");
            }
        }

        String finalRefs = references_buff.toString();
        //String finalSubject = MessageTextProcessor.decodeHeaderInArticleInfo(articleInfo.getSubject(), charset);
        String subject = articleInfo.getSubject();
        subject = subject.replaceAll(" +", " ");

        // Now insert the Article into the DB
        return DBUtils.insertArticleToGroupID(mGroupID, articleInfo, finalRefs, decodedfrom, subject, mContext,
                catchedDB);
    }

    // ====================================================================================
    // Get a header from the server, store it in the sdcard cache and return it
    // ====================================================================================
    private Header getAndCacheHeader(long headerTableId, String msgId, String group)
            throws ServerAuthException, IOException, UsenetReaderException {

        clientConnectIfNot();
        Reader reader = null;
        String strHeader = null;

        try {
            reader = (DotTerminatedMessageReader) mClient.retrieveArticleHeader(msgId);
        } catch (IOException e) {
            // Needed now???
            connect();
            selectNewsGroup(mGroup, false);
            reader = (DotTerminatedMessageReader) mClient.retrieveArticleHeader(msgId);
        }

        if (reader == null)
            return null;

        strHeader = MessageTextProcessor.readerToString(reader);

        if (strHeader == null)
            return null;

        // Now we have the header from the server, store it into the sdcard
        String outputPath = UsenetConstants.EXTERNALSTORAGE + "/" + UsenetConstants.APPNAME
                + "/offlinecache/groups/" + group + "/header/";
        FSUtils.writeStringToDiskFile(strHeader, outputPath, Long.toString(headerTableId));
        DBUtils.setMessageCatched(headerTableId, true, mContext);

        return MessageTextProcessor.strToHeader(strHeader);
    }

    // ====================================================================================
    // Get a body from the server and store it in the sdcard cache
    // ====================================================================================

    private FileReader getAndCacheBody(long headerTableId, String msgId, String group)
            throws ServerAuthException, IOException, UsenetReaderException {
        clientConnectIfNot();

        Reader reader = null;

        try {
            reader = (DotTerminatedMessageReader) mClient.retrieveArticleBody(msgId);
        } catch (IOException e) {
            connect();
            selectNewsGroup(mGroup, false);
            reader = (DotTerminatedMessageReader) mClient.retrieveArticleBody(msgId);
        }

        if (reader == null)
            return null;

        // Now we have the header from the server, store it into the sdcard
        String outputPath = UsenetConstants.EXTERNALSTORAGE + "/" + UsenetConstants.APPNAME
                + "/offlinecache/groups/" + group + "/body/";
        String fileName = Long.toString(headerTableId);
        FSUtils.writeReaderToDiskFile(reader, outputPath, fileName);
        DBUtils.setMessageCatched(headerTableId, true, mContext);

        return FSUtils.getReaderFromDiskFile(outputPath + "/" + fileName, true);
    }

    // ===================================================
    // Read a header from the cache
    // ===================================================
    private Header readHeaderFromCache(long id, String msgId, String group)
            throws UsenetReaderException, IOException, ServerAuthException {

        String header = null;
        String headerFilePath = UsenetConstants.EXTERNALSTORAGE + "/" + UsenetConstants.APPNAME
                + "/offlinecache/groups/" + group + "/header/" + id;
        File f = new File(headerFilePath);

        if (!f.exists()) {
            // For some odd reason, its not on the disk, fetch it from the net and write it to the cache
            Log.d(UsenetConstants.APPNAME, "Message supposedly catched wasn't; catching from the net");
            return getAndCacheHeader(id, msgId, group);
        } else {
            header = FSUtils.loadStringFromDiskFile(headerFilePath, true);
        }

        return MessageTextProcessor.strToHeader(header);
    }

    // ===================================================
    // Read a body from the cache
    // ===================================================
    private FileReader getBodyReaderFromCache(long id, String msgId, String group)
            throws UsenetReaderException, IOException, ServerAuthException {

        FileReader bodyReader = null;
        String partFilePath = UsenetConstants.EXTERNALSTORAGE + "/" + UsenetConstants.APPNAME
                + "/offlinecache/groups/" + group + "/body/" + id;
        File f = new File(partFilePath);

        if (!f.exists()) {
            // For some odd reason, its not on the disk, fetch it from the net and write it to the cache
            Log.d(UsenetConstants.APPNAME, "Message supposedly catched wasn't; catching from the net");
            bodyReader = getAndCacheBody(id, msgId, group);
        } else {
            //body = FSUtils.loadStringFromDiskFile(partFilePath, true);
            bodyReader = new FileReader(partFilePath);
        }

        return bodyReader;
    }

    // ================================================================================================================
    // Get a header for and article id (with msgId given too). If the article is not in the cache, it will request it 
    // for the server to be stored into the cache (if we're not in offline mode.) If the article is in the cache, this
    // will just read the article
    // ================================================================================================================

    public Header getHeader(long id, String msgId, boolean isoffline, boolean iscatched)
            throws UsenetReaderException, ServerAuthException, IOException {

        Header headerObj = null;
        //String header = null;
        //HashMap<String, String> headerTable = null;

        if (!iscatched) { // Not catched, cache it and get the result
            if (isoffline)
                throw new UsenetReaderException("Offline mode enabled but header " + id + " not catched");
            else
                headerObj = getAndCacheHeader(id, msgId, mGroup);

        } else // Catched, read if from the cache
            headerObj = readHeaderFromCache(id, msgId, mGroup);

        return headerObj;
    }

    // ===================================================================================
    // See the comment on getHeader, this is the same but getting the body and returning a
    // String instead of a Hashtable
    // ===================================================================================
    public Reader getBody(long id, String msgId, boolean isoffline, boolean iscatched)
            throws UsenetReaderException, ServerAuthException, IOException {
        Reader body = null;

        if (!iscatched) {
            if (isoffline)
                throw new UsenetReaderException("Offline mode enabled but bodytext for " + id + " not catched");
            else {
                body = getAndCacheBody(id, msgId, mGroup);
            }
        } else {
            body = getBodyReaderFromCache(id, msgId, mGroup);
        }
        return body;
    }

    // =============================================================================================
    // Construct a Message object with the given Header and the body taken from the cache or the net
    // =============================================================================================
    public Message getMessage(Header header, long id, String msgId, boolean isoffline, boolean iscatched,
            String charset, File internalCacheDir)

            throws UsenetReaderException, ServerAuthException, IOException {

        Message message = null;

        Reader bodyReader = getBody(id, msgId, isoffline, iscatched);
        if (bodyReader == null || header == null)
            throw new UsenetReaderException("Error getting body or header");

        //String messageStr = header.toString() + "\r\n" + strBody;
        StringReader headerReader = new StringReader(header.toString() + "\r\n");
        Vector<Reader> readers = new Vector<Reader>();
        readers.add(headerReader);
        readers.add(bodyReader);
        MergeReader messageReader = new MergeReader(readers);

        // Store in memory until 2MB of message size, then use the disk
        TempFileStorageProvider cacheProvider = new TempFileStorageProvider(internalCacheDir);
        ThresholdStorageProvider storageProvider = new ThresholdStorageProvider(cacheProvider, 2097152);
        DefaultStorageProvider.setInstance(storageProvider);

        MimeEntityConfig mimeConfig = new MimeEntityConfig();
        mimeConfig.setMaxLineLen(-1);
        ReaderInputStream msgStream = new ReaderInputStream(messageReader);
        message = new Message(msgStream, mimeConfig, charset);

        return message;
    }

    // ========================
    // List newsgroups
    // ========================
    public NewsgroupInfo[] listNewsgroups(String wildmat) throws IOException, ServerAuthException {
        clientConnectIfNot();

        NewsgroupInfo[] retVal = null;
        try {
            retVal = mClient.listNewsgroups(wildmat);
        } catch (IOException e) {
            connect();
            retVal = mClient.listNewsgroups(wildmat);
        }

        return retVal;
    }

    public void postArticle(String fullMessage, boolean forceOnline)
            throws IOException, ServerAuthException, UsenetReaderException {

        String error = null;
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);

        boolean offlineMode;

        if (forceOnline)
            offlineMode = false;
        else
            offlineMode = prefs.getBoolean("offlineMode", true);

        boolean postInOffline = prefs.getBoolean("postDirectlyInOfflineMode", true);
        boolean saveToOutbox = offlineMode && !postInOffline;

        if (!saveToOutbox) {
            connect();

            Writer writer = mClient.postArticle();

            if (writer != null) {
                writer.write(fullMessage);
                writer.close();
                if (!mClient.completePendingCommand()) {
                    error = mClient.getReplyString();
                    saveToOutbox = true; // This will make it try to save to the outbox
                }
            }
        }

        if (saveToOutbox) {
            String saveError = ServerManager.saveMessageToOutbox(fullMessage, mContext);

            if (saveError != null) {
                if (error != null)
                    error = error + "; and saving error: " + saveError;
                else
                    error = saveError;
            }
        }

        if (error != null)
            throw new UsenetReaderException(error);

    }

    public void postArticle(String header, String body, String signature)
            throws IOException, ServerAuthException, UsenetReaderException {
        StringBuilder fullMessage = new StringBuilder();

        fullMessage.append(header.trim());
        fullMessage.append("\r\n\r\n");
        fullMessage.append(body.trim());
        fullMessage.append(signature);
        postArticle(fullMessage.toString(), false);
    }

    private static String saveMessageToOutbox(String fullMessage, Context context) {

        String error = null;

        long outid = DBUtils.insertOfflineSentPost(context);

        String outputDir = UsenetConstants.EXTERNALSTORAGE + "/" + UsenetConstants.APPNAME
                + "/offlinecache/outbox/";
        File outDir = new File(outputDir);
        if (!outDir.exists())
            outDir.mkdirs();

        File outFile = new File(outDir, Long.toString(outid));
        BufferedWriter out = null;

        try {
            FileWriter writer = new FileWriter(outFile);
            out = new BufferedWriter(writer);
            out.write(fullMessage);
            out.flush();
        } catch (IOException e) {
            error = e.getMessage();
        } finally {
            try {
                if (out != null)
                    out.close();
            } catch (IOException e) {
            }
        }

        return error;
    }

    public void setFetchLatest(boolean getLatestOption) {
        /* Enable or disable the fetching of the newest messages in the group (getNewOption=true) or the oldest */
        this.mFetchLatest = getLatestOption;

    }

    public boolean getFetchLatest() {
        return this.mFetchLatest;
    }

}