mirror of https://github.com/M66B/FairEmail.git
Added send partial
This commit is contained in:
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…
Reference in New Issue