Attachment download

This commit is contained in:
M66B 2018-08-03 19:12:19 +00:00
parent bc9a26c2c7
commit 70e0913331
50 changed files with 205 additions and 112 deletions

1
FAQ.md
View File

@ -21,6 +21,7 @@ The low priority status bar notification shows the number of pending operations,
* Move message to another remote folder
* Delete message from remote folder
* Send message
* Download attachment
<a name="FAQ3"></a>
**(3) What is a valid security certificate?**

View File

@ -28,15 +28,19 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.ViewHolder> {
private Context context;
private ExecutorService executor = Executors.newCachedThreadPool();
private List<EntityAttachment> all = new ArrayList<>();
private List<EntityAttachment> filtered = new ArrayList<>();
@ -45,30 +49,39 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
implements View.OnClickListener {
View itemView;
TextView tvName;
TextView tvType;
TextView tvSize;
ImageView ivDownload;
ViewHolder(View itemView) {
super(itemView);
this.itemView = itemView;
tvName = itemView.findViewById(R.id.tvName);
tvType = itemView.findViewById(R.id.tvType);
tvSize = itemView.findViewById(R.id.tvSize);
ivDownload = itemView.findViewById(R.id.ivDownload);
}
private void wire() {
itemView.setOnClickListener(this);
ivDownload.setOnClickListener(this);
}
private void unwire() {
itemView.setOnClickListener(null);
ivDownload.setOnClickListener(null);
}
@Override
public void onClick(View view) {
EntityAttachment attachment = filtered.get(getLayoutPosition());
if (attachment.content == null) {
final EntityAttachment attachment = filtered.get(getLayoutPosition());
if (attachment != null && attachment.content == null)
executor.submit(new Runnable() {
@Override
public void run() {
EntityMessage message = DB.getInstance(context).message().getMessage(attachment.message);
EntityOperation.queue(context, message, EntityOperation.ATTACHMENT, attachment.sequence);
}
});
}
}
@ -175,7 +188,11 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
EntityAttachment attachment = filtered.get(position);
holder.tvName.setText(attachment.name);
holder.tvType.setText(attachment.type);
holder.tvSize.setVisibility((attachment.content == null ? View.GONE : View.VISIBLE));
holder.ivDownload.setVisibility((attachment.content == null ? View.VISIBLE : View.GONE));
if (attachment.content != null)
holder.tvSize.setText(Helper.humanReadableByteCount(attachment.content.length, false));
holder.wire();
}

View File

@ -24,6 +24,7 @@ import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.OnConflictStrategy;
import android.arch.persistence.room.Query;
import android.arch.persistence.room.Update;
import java.util.List;
@ -32,6 +33,12 @@ public interface DaoAttachment {
@Query("SELECT * FROM attachment WHERE message = :message")
LiveData<List<EntityAttachment>> liveAttachments(long message);
@Query("SELECT * FROM attachment WHERE message = :message AND sequence = :sequence")
EntityAttachment getAttachment(long message, int sequence);
@Insert(onConflict = OnConflictStrategy.REPLACE)
long insertAttachment(EntityAttachment attachment);
@Update
void updateAttachment(EntityAttachment attachment);
}

View File

@ -21,10 +21,13 @@ package eu.faircode.email;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.ForeignKey;
import android.arch.persistence.room.Ignore;
import android.arch.persistence.room.Index;
import android.arch.persistence.room.PrimaryKey;
import android.support.annotation.NonNull;
import javax.mail.BodyPart;
import static android.arch.persistence.room.ForeignKey.CASCADE;
@Entity(
@ -50,4 +53,7 @@ public class EntityAttachment {
@NonNull
public String type;
public byte[] content;
@Ignore
BodyPart part;
}

View File

@ -58,6 +58,7 @@ public class EntityOperation {
public static final String MOVE = "move";
public static final String DELETE = "delete";
public static final String SEND = "send";
public static final String ATTACHMENT = "attachment";
static void queue(Context context, EntityMessage message, String name) {
JSONArray jsonArray = new JSONArray();

View File

@ -67,6 +67,14 @@ public class Helper {
return sb.toString();
}
static String humanReadableByteCount(long bytes, boolean si) {
int unit = si ? 1000 : 1024;
if (bytes < unit) return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(unit));
String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
}
static StringBuilder getDebugInfo() {
StringBuilder sb = new StringBuilder();

View File

@ -313,6 +313,7 @@ public class MessageHelper {
attachment.sequence = result.size() + 1;
attachment.name = part.getFileName();
attachment.type = ct.getBaseType();
attachment.part = part;
result.add(attachment);
}
} else if (content instanceof Multipart) {

View File

@ -50,7 +50,9 @@ import com.sun.mail.imap.protocol.IMAPProtocol;
import org.json.JSONArray;
import org.json.JSONException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
@ -566,16 +568,17 @@ public class ServiceSynchronize extends LifecycleService {
}
}
private void processOperations(EntityFolder folder, IMAPStore istore, IMAPFolder ifolder) throws MessagingException, JSONException {
private void processOperations(EntityFolder folder, IMAPStore istore, IMAPFolder ifolder) throws MessagingException, JSONException, IOException {
try {
Log.i(Helper.TAG, folder.name + " start process");
DB db = DB.getInstance(this);
DaoOperation operation = db.operation();
DaoMessage message = db.message();
for (TupleOperationEx op : operation.getOperations(folder.id)) {
for (TupleOperationEx op : operation.getOperations(folder.id))
try {
Log.i(Helper.TAG, folder.name +
" Process op=" + op.id + "/" + op.name +
" start op=" + op.id + "/" + op.name +
" args=" + op.args +
" msg=" + op.message);
@ -671,6 +674,30 @@ public class ServiceSynchronize extends LifecycleService {
itransport.close();
}
} else if (EntityOperation.ATTACHMENT.equals(op.name)) {
int sequence = jargs.getInt(0);
EntityAttachment attachment = db.attachment().getAttachment(op.message, sequence);
Message imessage = ifolder.getMessageByUID(op.uid);
if (imessage == null)
throw new MessageRemovedException();
Properties props = MessageHelper.getSessionProperties();
Session isession = Session.getDefaultInstance(props, null);
MessageHelper helper = new MessageHelper((MimeMessage) imessage);
EntityAttachment a = helper.getAttachments().get(sequence - 1);
InputStream is = a.part.getInputStream();
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
for (int len = is.read(buffer); len != -1; len = is.read(buffer))
os.write(buffer, 0, len);
attachment.content = os.toByteArray();
db.attachment().updateAttachment(attachment);
Log.i(Helper.TAG, "Downloaded bytes=" + attachment.content.length);
} else
throw new MessagingException("Unknown operation name=" + op.name);
@ -683,6 +710,8 @@ public class ServiceSynchronize extends LifecycleService {
// There is no use in repeating
operation.deleteOperation(op.id);
}
} finally {
Log.i(Helper.TAG, folder.name + " end op=" + op.id + "/" + op.name);
}
} finally {
Log.i(Helper.TAG, folder.name + " end process");

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
</vector>

View File

@ -16,25 +16,38 @@
<TextView
android:id="@+id/tvName"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:ellipsize="marquee"
android:singleLine="true"
android:text="Name"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintBottom_toBottomOf="@id/ivAttachments"
app:layout_constraintEnd_toStartOf="@+id/tvSize"
app:layout_constraintStart_toEndOf="@id/ivAttachments"
app:layout_constraintTop_toTopOf="@id/ivAttachments" />
<TextView
android:id="@+id/tvType"
android:layout_width="0dp"
android:id="@+id/tvSize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:gravity="end"
android:text="Type"
android:text="10 kB"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintBottom_toBottomOf="@id/ivAttachments"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@+id/ivDownload"
app:layout_constraintStart_toEndOf="@id/tvName"
app:layout_constraintTop_toTopOf="@id/ivAttachments" />
<ImageView
android:id="@+id/ivDownload"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="6dp"
android:layout_marginStart="6dp"
android:src="@drawable/baseline_get_app_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tvSize"
app:layout_constraintTop_toTopOf="@id/ivAttachments" />
</android.support.constraint.ConstraintLayout>