/*
* Copyright 2010 Arthur Zaczek <arthur@dasz.at>, dasz.at OG; All rights reserved.
* Copyright 2010 David Schmitt <david@dasz.at>, dasz.at OG; All rights reserved.
*
* This file is part of Kolab Sync for Android.
* Kolab Sync for Android 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.
* Kolab Sync for Android 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 Kolab Sync for Android.
* If not, see <http://www.gnu.org/licenses/>.
*/
package at.dasz.KolabDroid.Sync;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.BodyPart;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.MultipartDataSource;
import javax.mail.Session;
import javax.mail.Flags.Flag;
import javax.mail.Message.RecipientType;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.util.ByteArrayDataSource;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import android.content.Context;
import android.text.format.Time;
import android.util.Log;
import at.dasz.KolabDroid.Utils;
import at.dasz.KolabDroid.Settings.Settings;
/**
* This class contains common code for all local data stores and defines the
* store-specific functionality that has to be implemented by the store.
*/
public abstract class AbstractSyncHandler implements SyncHandler
{
protected AbstractSyncHandler(Context context)
{
this.context = context;
status = new StatusEntry();
Time t = new Time();
t.setToNow();
status.setTime(t);
}
protected StatusEntry status;
protected Context context;
protected Settings settings;
protected abstract String getMimeType();
/**
* Create the XML in kolab format to describe the specified item.
*
* @param sync
* @return String containing the XML describing the item
* @throws ParserConfigurationException
* @throws SyncException
* @throws MessagingException
*/
protected abstract String writeXml(SyncContext sync)
throws ParserConfigurationException, SyncException, MessagingException;
/**
* Create a human readable description of the specified item to be displayed
* as text/plain part in the mail.
* @param sync
* @return
* @throws SyncException
* @throws MessagingException
*/
protected abstract String getMessageBodyText(SyncContext sync) throws SyncException, MessagingException;
/**
* Create a short human readable string describing an item for log messages.
* @param sync
* @return
* @throws MessagingException
*/
public abstract String getItemText(SyncContext sync) throws MessagingException;
/**
* Update the local item from the specified XML Document.
* @param sync
* @param xml
* @throws SyncException
*/
protected abstract void updateLocalItemFromServer(SyncContext sync,
Document xml) throws SyncException;
/**
* Update the specified XML Document from the local item.
* @param sync
* @param xml
* @throws SyncException
* @throws MessagingException
*/
protected abstract void updateServerItemFromLocal(SyncContext sync,
Document xml) throws SyncException, MessagingException;
/**
* Delete the local item with the specified ID.
* @param localId
* @throws SyncException
*/
public abstract void deleteLocalItem(int localId) throws SyncException;
public StatusEntry getStatus()
{
return status;
}
//public void createLocalItemFromServer(SyncContext sync)
public void createLocalItemFromServer(Session session, Folder folder, SyncContext sync)
throws MessagingException, ParserConfigurationException,
IOException, SyncException
{
Log.d("sync", "Downloading item ...");
try
{
InputStream xmlinput = extractXml(sync.getMessage());
Document doc = Utils.getDocument(xmlinput);
updateLocalItemFromServer(sync, doc);
updateCacheEntryFromMessage(sync);
}
catch (SAXException ex)
{
throw new SyncException(getItemText(sync),
"Unable to extract XML Document", ex);
}
}
public void updateLocalItemFromServer(SyncContext sync)
throws MessagingException, ParserConfigurationException,
IOException, SyncException
{
if (hasLocalItem(sync))
{
Log.d("sync", "Updating without conflict check: "
+ sync.getCacheEntry().getLocalId());
try
{
InputStream xmlinput = extractXml(sync.getMessage());
Document doc = Utils.getDocument(xmlinput);
updateLocalItemFromServer(sync, doc);
updateCacheEntryFromMessage(sync);
}
catch (SAXException ex)
{
throw new SyncException(getItemText(sync),
"Unable to extract XML Document", ex);
}
}
}
protected void updateCacheEntryFromMessage(SyncContext sync)
throws MessagingException
{
CacheEntry c = sync.getCacheEntry();
Message m = sync.getMessage();
Date dt = Utils.getMailDate(m);
c.setRemoteChangedDate(dt);
c.setRemoteId(m.getSubject());
c.setRemoteSize(m.getSize());
//set correct remote hash
InputStream is = null;
try
{
DataSource mainDataSource = m.getDataHandler()
.getDataSource();
if ((mainDataSource instanceof MultipartDataSource))
{
MultipartDataSource multipart = (MultipartDataSource) mainDataSource;
for (int idx = 0; idx < multipart.getCount(); idx++)
{
BodyPart p = multipart.getBodyPart(idx);
if (!p.isMimeType("text/plain"))
{
is = p.getInputStream();
break;
}
}
}
else
{
MimeMultipart multipart = (MimeMultipart) m.getContent();
for (int idx = 0; idx < multipart.getCount(); idx++)
{
BodyPart p = multipart.getBodyPart(idx);
if (!p.isMimeType("text/plain"))
{
is = p.getInputStream();
break;
}
}
}
}
catch (Exception ex)
{
ex.printStackTrace();
}
if(is != null)
{
Document doc = null;
try
{
doc = Utils.getDocument(is);
String docText = Utils.getXml(doc.getDocumentElement());
byte[] remoteHash = Utils.sha1Hash(docText);
c.setRemoteHash(remoteHash);
}
catch (Exception ex)
{
Log.e("EE", ex.toString());
}
}
getLocalCacheProvider().saveEntry(c);
}
public void createServerItemFromLocal(Session session, Folder targetFolder,
SyncContext sync, int localId) throws MessagingException,
ParserConfigurationException, SyncException
{
Log.d("sync", "Uploading: #" + localId);
// initialize cache entry with values that should go
// into the new server item
CacheEntry entry = new CacheEntry();
entry.setLocalId(localId);
sync.setCacheEntry(entry);
String xml = writeXml(sync);
Message m = wrapXmlInMessage(session, sync, xml);
targetFolder.appendMessages(new Message[] { m });
m.saveChanges();
sync.setMessage(m);
updateCacheEntryFromMessage(sync);
}
public void updateServerItemFromLocal(Session session, Folder targetFolder,
SyncContext sync) throws MessagingException, IOException,
SyncException, ParserConfigurationException
{
Log.d("sync", "Update item on Server: #"
+ sync.getCacheEntry().getLocalId());
InputStream xmlinput = extractXml(sync.getMessage());
try
{
// Parse XML
Document doc = Utils.getDocument(xmlinput);
// Update
updateServerItemFromLocal(sync, doc);
// Create & Upload new Message
// IMAP needs a new Message uploaded
String xml = Utils.getXml(doc);
Message newMessage = wrapXmlInMessage(session, sync, xml);
targetFolder.appendMessages(new Message[] { newMessage });
newMessage.saveChanges();
// Delete old message
sync.getMessage().setFlag(Flag.DELETED, true);
// Replace sync context with new message
sync.setMessage(newMessage);
updateCacheEntryFromMessage(sync);
}
catch (SAXException ex)
{
throw new SyncException(getItemText(sync),
"Unable to extract XML Document", ex);
}
finally
{
if (xmlinput != null) xmlinput.close();
}
}
public void deleteLocalItem(SyncContext sync) throws SyncException
{
Log.d("sync", "Deleting locally: "
+ sync.getCacheEntry().getLocalHash());
deleteLocalItem(sync.getCacheEntry().getLocalId());
getLocalCacheProvider().deleteEntry(sync.getCacheEntry());
}
public void deleteServerItem(SyncContext sync) throws MessagingException,
SyncException
{
Log
.d("sync", "Deleting from server: "
+ sync.getMessage().getSubject());
sync.getMessage().setFlag(Flag.DELETED, true);
// remove contents too, to avoid confusing the butchered JAF
// message.setContent("", "text/plain");
// message.saveChanges();
getLocalCacheProvider().deleteEntry(sync.getCacheEntry());
}
//private InputStream extractXml(Message message)
protected InputStream extractXml(Message message)
{
try
{
DataSource mainDataSource = message.getDataHandler()
.getDataSource();
if (!(mainDataSource instanceof MultipartDataSource)) { return null; }
MultipartDataSource multipart = (MultipartDataSource) mainDataSource;
for (int idx = 0; idx < multipart.getCount(); idx++)
{
BodyPart p = multipart.getBodyPart(idx);
if (p.isMimeType(getMimeType())) { return p.getInputStream(); }
}
}
catch (MessagingException ex)
{
ex.printStackTrace();
}
catch (IOException ex)
{
ex.printStackTrace();
}
return null;
}
protected Message wrapXmlInMessage(Session session, SyncContext sync,
String xml) throws MessagingException, SyncException
{
Message result = new MimeMessage(session);
result.setSubject(sync.getCacheEntry().getRemoteId());
result.setSentDate(sync.getCacheEntry().getRemoteChangedDate());
result.setFrom(new InternetAddress("kolab-android@dasz.at"));
result.setRecipient(RecipientType.TO, new InternetAddress("kolab-android@dasz.at"));
result.setHeader("User-Agent", "kolab-android 0.1");
result.setHeader("X-Kolab-Type", getMimeType());
MimeMultipart mp = new MimeMultipart();
MimeBodyPart txt = new MimeBodyPart();
txt.setText(getMessageBodyText(sync), "utf-8");
mp.addBodyPart(txt);
BodyPart messageBodyPart = new MimeBodyPart();
DataSource source;
try
{
source = new ByteArrayDataSource(xml.getBytes("UTF-8"),
getMimeType());
}
catch (UnsupportedEncodingException ex)
{
ex.printStackTrace();
throw new RuntimeException(ex);
}
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName("kolab.xml");
mp.addBodyPart(messageBodyPart);
result.setContent(mp);
// avoid later change in timestamp when the SEEN flag would be updated
result.setFlag(Flags.Flag.SEEN, true);
return result;
}
public Settings getSettings()
{
return this.settings;
}
public void setSettings(Settings settings)
{
this.settings = settings;
}
}