Added send partial

pull/214/head
M66B 10 months ago
parent 456dc1be92
commit edc0147868

@ -32,6 +32,8 @@ import androidx.room.Update;
import java.util.List;
import javax.mail.Address;
@Dao
public interface DaoMessage {
@ -761,6 +763,15 @@ public interface DaoMessage {
@Query("UPDATE message SET fts = :fts WHERE id = :id AND NOT (fts IS :fts)")
int setMessageFts(long id, boolean fts);
@Query("UPDATE message SET `to` = :to WHERE id = :id AND NOT (`to` IS :to)")
int setMessageTo(long id, String to);
@Query("UPDATE message SET `cc` = :cc WHERE id = :id AND NOT (`cc` IS :cc)")
int setMessageCc(long id, String cc);
@Query("UPDATE message SET `bcc` = :bcc WHERE id = :id AND NOT (`bcc` IS :bcc)")
int setMessageBcc(long id, String bcc);
@Query("UPDATE message SET received = :received WHERE id = :id AND NOT (received IS :received)")
int setMessageReceived(long id, long received);
@ -837,6 +848,9 @@ public interface DaoMessage {
" AND (NOT (received IS :sent) OR NOT (sent IS :sent))")
int setMessageSent(long id, Long sent);
@Query("UPDATE message SET warning = :warning WHERE id = :id AND NOT (warning IS :warning)")
int setMessageWarning(long id, String warning);
@Query("UPDATE message SET error = :error WHERE id = :id AND NOT (error IS :error)")
int setMessageError(long id, String error);

@ -332,6 +332,10 @@ public class EmailService implements AutoCloseable {
properties.put("mail." + protocol + ".ignorebodystructuresize", Boolean.toString(enabled));
}
void setSendPartial(boolean enabled) {
properties.put("mail." + protocol + ".sendpartial", Boolean.toString(enabled));
}
void setUseIp(boolean enabled, String host) {
this.useip = enabled;
this.ehlo = host;

@ -3040,10 +3040,17 @@ public class FragmentMessages extends FragmentBase
swipes.left_type = null;
} else if (EntityFolder.OUTBOX.equals(message.folderType)) {
swipes = new TupleAccountSwipes();
swipes.swipe_right = 0L;
swipes.right_type = EntityFolder.DRAFTS;
swipes.swipe_left = 0L;
swipes.left_type = EntityFolder.DRAFTS;
if (message.warning == null) {
swipes.swipe_right = 0L;
swipes.right_type = EntityFolder.DRAFTS;
swipes.swipe_left = 0L;
swipes.left_type = EntityFolder.DRAFTS;
} else {
swipes.swipe_right = EntityMessage.SWIPE_ACTION_DELETE;
swipes.right_type = null;
swipes.swipe_left = EntityMessage.SWIPE_ACTION_DELETE;
swipes.left_type = null;
}
} else {
swipes = accountSwipes.get(message.account);
if (swipes == null)
@ -3200,7 +3207,10 @@ public class FragmentMessages extends FragmentBase
}
if (EntityFolder.OUTBOX.equals(message.folderType)) {
ActivityCompose.undoSend(message.id, getContext(), getViewLifecycleOwner(), getParentFragmentManager());
if (message.warning == null)
ActivityCompose.undoSend(message.id, getContext(), getViewLifecycleOwner(), getParentFragmentManager());
else
onDelete(message.id);
return;
}

@ -128,6 +128,7 @@ public class FragmentOptionsSend extends FragmentBase implements SharedPreferenc
private SwitchCompat swReplyMove;
private SwitchCompat swReplyMoveInbox;
private EditText etSendRetryMax;
private SwitchCompat swSendPartial;
private final static List<String> RESET_OPTIONS = Collections.unmodifiableList(Arrays.asList(
"keyboard", "keyboard_no_fullscreen",
@ -148,7 +149,7 @@ public class FragmentOptionsSend extends FragmentBase implements SharedPreferenc
"receipt_default", "receipt_type", "receipt_legacy",
"forward_new",
"lookup_mx", "reply_move", "reply_move_inbox",
"send_retry_max"
"send_retry_max", "send_partial"
));
@Override
@ -222,6 +223,7 @@ public class FragmentOptionsSend extends FragmentBase implements SharedPreferenc
swReplyMove = view.findViewById(R.id.swReplyMove);
swReplyMoveInbox = view.findViewById(R.id.swReplyMoveInbox);
etSendRetryMax = view.findViewById(R.id.etSendRetryMax);
swSendPartial = view.findViewById(R.id.swSendPartial);
List<StyleHelper.FontDescriptor> fonts = StyleHelper.getFonts(getContext(), false);
@ -786,6 +788,13 @@ public class FragmentOptionsSend extends FragmentBase implements SharedPreferenc
}
});
swSendPartial.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
prefs.edit().putBoolean("send_partial", checked).apply();
}
});
// Initialize
FragmentDialogTheme.setBackground(getContext(), view, false);
@ -968,6 +977,8 @@ public class FragmentOptionsSend extends FragmentBase implements SharedPreferenc
int send_retry_max = prefs.getInt("send_retry_max", 0);
etSendRetryMax.setText(send_retry_max > 0 ? Integer.toString(send_retry_max) : null);
etSendRetryMax.setHint(Integer.toString(ServiceSend.RETRY_MAX_DEFAULT));
swSendPartial.setChecked(prefs.getBoolean("send_partial", false));
} catch (Throwable ex) {
Log.e(ex);
}

@ -5677,6 +5677,25 @@ public class MessageHelper {
return false;
}
static Address[] removeAddresses(Address[] addresses, List<Address> removes) {
if (addresses == null || addresses.length == 0)
return new Address[0];
List<Address> result = new ArrayList<>();
for (Address address : addresses) {
boolean found = false;
for (Address remove : removes)
if (equalEmail(address, remove)) {
found = true;
break;
}
if (!found)
result.add(address);
}
return result.toArray(new Address[0]);
}
static boolean equalEmail(Address a1, Address a2) {
String email1 = ((InternetAddress) a1).getAddress();
String email2 = ((InternetAddress) a2).getAddress();

@ -294,7 +294,7 @@ public class ServiceSend extends ServiceBase implements SharedPreferences.OnShar
.setSmallIcon(R.drawable.baseline_warning_white_24)
.setContentTitle(getString(R.string.title_notification_sending_failed, recipient))
.setContentIntent(getPendingIntent(this))
.setAutoCancel(tries_left != 0)
.setAutoCancel(true)
.setShowWhen(true)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setOnlyAlertOnce(false)
@ -573,7 +573,7 @@ public class ServiceSend extends ServiceBase implements SharedPreferences.OnShar
// Requeue non executing operations
for (long id : db.message().getMessageByFolder(outbox.id)) {
EntityMessage message = db.message().getMessage(id);
if (message == null)
if (message == null || message.warning != null)
continue;
EntityOperation op = db.operation().getOperation(message.id, EntityOperation.SEND);
@ -623,6 +623,7 @@ public class ServiceSend extends ServiceBase implements SharedPreferences.OnShar
nm.notify(NotificationHelper.NOTIFICATION_SEND, getNotificationService(true));
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean send_partial = prefs.getBoolean("send_partial", false);
boolean reply_move = prefs.getBoolean("reply_move", false);
boolean reply_move_inbox = prefs.getBoolean("reply_move_inbox", true);
boolean protocol = prefs.getBoolean("protocol", false);
@ -761,8 +762,10 @@ public class ServiceSend extends ServiceBase implements SharedPreferences.OnShar
}
// Create transport
long start, end;
long start = 0;
long end = 0;
Long max_size = null;
SMTPSendFailedException partial = null;
if (ident.auth_type == AUTH_TYPE_GRAPH) {
start = new Date().getTime();
MicrosoftGraph.send(ServiceSend.this, ident, imessage);
@ -770,6 +773,8 @@ public class ServiceSend extends ServiceBase implements SharedPreferences.OnShar
} else {
EmailService iservice = new EmailService(this, ident, EmailService.PURPOSE_USE, debug);
try {
if (send_partial)
iservice.setSendPartial(true);
iservice.setUseIp(ident.use_ip, ident.ehlo);
if (!message.isSigned() && !message.isEncrypted())
iservice.set8BitMime(ident.octetmime);
@ -820,6 +825,12 @@ public class ServiceSend extends ServiceBase implements SharedPreferences.OnShar
recipients.add(a);
}
if (BuildConfig.DEBUG && false) {
InternetAddress invalid = new InternetAddress();
invalid.setAddress("invalid");
recipients.add(invalid);
}
if (protocol && BuildConfig.DEBUG) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
imessage.writeTo(bos);
@ -855,15 +866,27 @@ public class ServiceSend extends ServiceBase implements SharedPreferences.OnShar
// Send message
EntityLog.log(this, "Sending " + via);
start = new Date().getTime();
iservice.getTransport().sendMessage(imessage, rcptto);
end = new Date().getTime();
try {
iservice.getTransport().sendMessage(imessage, rcptto);
} finally {
end = new Date().getTime();
}
EntityLog.log(this, "Sent " + via + " elapse=" + (end - start) + " ms");
} catch (MessagingException ex) {
iservice.dump(ident.email);
Log.e(ex);
if (ex instanceof SMTPSendFailedException) {
SMTPSendFailedException sem = (SMTPSendFailedException) ex;
if (send_partial &&
sem.getInvalidAddresses() != null &&
sem.getValidSentAddresses() != null &&
sem.getValidUnsentAddresses() != null &&
sem.getValidSentAddresses().length > 0 &&
sem.getInvalidAddresses().length + sem.getValidUnsentAddresses().length > 0) {
partial = sem;
}
ex = new SMTPSendFailedException(
sem.getCommand(),
sem.getReturnCode(),
@ -874,12 +897,13 @@ public class ServiceSend extends ServiceBase implements SharedPreferences.OnShar
sem.getInvalidAddresses());
}
if (sid != null)
if (sid != null && partial == null)
db.message().deleteMessage(sid);
db.identity().setIdentityError(ident.id, Log.formatThrowable(ex));
throw ex;
if (partial == null)
throw ex;
} catch (Throwable ex) {
iservice.dump(ident.email);
throw ex;
@ -898,7 +922,20 @@ public class ServiceSend extends ServiceBase implements SharedPreferences.OnShar
db.beginTransaction();
// Delete from outbox
db.message().deleteMessage(message.id);
if (partial == null)
db.message().deleteMessage(message.id);
else {
Throwable ex = new Throwable(getString(R.string.title_advanced_sent_partially), partial);
db.message().setMessageWarning(message.id, Log.formatThrowable(ex));
if (NotificationHelper.areNotificationsEnabled(nm)) {
NotificationCompat.Builder builder = getNotificationError(
MessageHelper.formatAddressesShort(message.to),
ex, 0);
nm.notify("partial:" + message.id,
NotificationHelper.NOTIFICATION_TAGGED,
builder.build());
}
}
// Show in sent folder
if (sid != null) {
@ -907,6 +944,20 @@ public class ServiceSend extends ServiceBase implements SharedPreferences.OnShar
db.attachment().deleteAttachments(sid,
new int[]{EntityAttachment.PGP_MESSAGE, EntityAttachment.SMIME_MESSAGE});
if (partial != null) {
List<Address> unsent = new ArrayList<>();
if (partial.getInvalidAddresses() != null)
unsent.addAll(Arrays.asList(partial.getInvalidAddresses()));
if (partial.getValidUnsentAddresses() != null)
unsent.addAll(Arrays.asList(partial.getValidUnsentAddresses()));
db.message().setMessageTo(sid,
DB.Converters.encodeAddresses(MessageHelper.removeAddresses(message.to, unsent)));
db.message().setMessageCc(sid,
DB.Converters.encodeAddresses(MessageHelper.removeAddresses(message.cc, unsent)));
db.message().setMessageBcc(sid,
DB.Converters.encodeAddresses(MessageHelper.removeAddresses(message.bcc, unsent)));
}
db.message().setMessageReceived(sid, start);
db.message().setMessageSent(sid, end);
db.message().setMessageUiHide(sid, false);

@ -1158,6 +1158,28 @@
android:textStyle="italic"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/etSendRetryMax" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/swSendPartial"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_advanced_send_partial"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvSendRetryHint"
app:switchPadding="12dp" />
<TextView
android:id="@+id/tvSendPartialHint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="48dp"
android:text="@string/title_advanced_send_partial_hint"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textStyle="italic"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swSendPartial" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -501,6 +501,9 @@
<string name="title_advanced_reply_move_inbox">Also for messages in the inbox</string>
<string name="title_advanced_send_retry_max">Maximum send attempts</string>
<string name="title_advanced_send_retry_hint">Sending will be retried on connectivity changes</string>
<string name="title_advanced_send_partial">Allow sending to some recipients</string>
<string name="title_advanced_send_partial_hint">If some addresses are invalid, a message will still be sent to the other addresses</string>
<string name="title_advanced_sent_partially">The message was not sent to all recipients</string>
<string name="title_advanced_auto_link">Automatically create links</string>
<string name="title_advanced_plain_only">Send plain text only by default</string>

Loading…
Cancel
Save