Migrate avatars and group avatars.
This commit is contained in:
parent
9848599807
commit
10bfc8a753
22 changed files with 317 additions and 136 deletions
|
@ -20,6 +20,7 @@ package org.thoughtcrime.securesms;
|
|||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
|
@ -63,6 +64,7 @@ import org.thoughtcrime.securesms.mediasend.AvatarSelectionBottomSheetDialogFrag
|
|||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
|
@ -76,6 +78,7 @@ import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
|||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
|
@ -102,7 +105,6 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
|||
|
||||
private static final short REQUEST_CODE_SELECT_AVATAR = 26165;
|
||||
private static final int PICK_CONTACT = 1;
|
||||
public static final int AVATAR_SIZE = 210;
|
||||
|
||||
private EditText groupName;
|
||||
private ListView lv;
|
||||
|
@ -321,7 +323,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
|||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.centerCrop()
|
||||
.override(AVATAR_SIZE, AVATAR_SIZE)
|
||||
.override(AvatarHelper.AVATAR_DIMENSIONS, AvatarHelper.AVATAR_DIMENSIONS)
|
||||
.into(new SimpleTarget<Bitmap>() {
|
||||
@Override
|
||||
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
|
||||
|
@ -554,10 +556,16 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
|||
existingContacts.addAll(recipients);
|
||||
|
||||
if (group.isPresent()) {
|
||||
Bitmap avatar = null;
|
||||
try {
|
||||
avatar = BitmapFactory.decodeStream(AvatarHelper.getAvatar(getContext(), group.get().getRecipientId()));
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to read avatar.");
|
||||
}
|
||||
return Optional.of(new GroupData(groupIds[0],
|
||||
existingContacts,
|
||||
BitmapUtil.fromByteArray(group.get().getAvatar()),
|
||||
group.get().getAvatar(),
|
||||
avatar,
|
||||
BitmapUtil.toByteArray(avatar),
|
||||
group.get().getTitle()));
|
||||
} else {
|
||||
return Optional.absent();
|
||||
|
|
|
@ -21,7 +21,6 @@ import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
|
|||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
||||
import org.thoughtcrime.securesms.database.JobDatabase;
|
||||
import org.thoughtcrime.securesms.database.KeyValueDatabase;
|
||||
|
@ -49,7 +48,6 @@ import java.io.OutputStream;
|
|||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
@ -118,9 +116,11 @@ public class FullBackupExporter extends FullBackupBase {
|
|||
|
||||
stopwatch.split("prefs");
|
||||
|
||||
for (File avatar : AvatarHelper.getAvatarFiles(context)) {
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||
outputStream.write(avatar.getName(), new FileInputStream(avatar), avatar.length());
|
||||
for (AvatarHelper.Avatar avatar : AvatarHelper.getAvatars(context)) {
|
||||
if (avatar != null) {
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||
outputStream.write(avatar.getFilename(), avatar.getInputStream(), avatar.getLength());
|
||||
}
|
||||
}
|
||||
|
||||
stopwatch.split("avatars");
|
||||
|
|
|
@ -176,7 +176,7 @@ public class FullBackupImporter extends FullBackupBase {
|
|||
private static void processAvatar(@NonNull Context context, @NonNull SQLiteDatabase db, @NonNull BackupProtos.Avatar avatar, @NonNull BackupRecordInputStream inputStream) throws IOException {
|
||||
if (avatar.hasRecipientId()) {
|
||||
RecipientId recipientId = RecipientId.from(avatar.getRecipientId());
|
||||
inputStream.readAttachmentTo(new FileOutputStream(AvatarHelper.getAvatarFile(context, recipientId)), avatar.getLength());
|
||||
inputStream.readAttachmentTo(AvatarHelper.getOutputStream(context, recipientId), avatar.getLength());
|
||||
} else {
|
||||
if (avatar.hasName() && SqlUtil.tableExists(db, "recipient_preferences")) {
|
||||
Log.w(TAG, "Avatar is missing a recipientId. Clearing signal_profile_avatar (legacy) so it can be fetched later.");
|
||||
|
|
|
@ -10,6 +10,7 @@ import androidx.annotation.Nullable;
|
|||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.util.Conversions;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
|
@ -33,11 +34,11 @@ public final class GroupRecordContactPhoto implements ContactPhoto {
|
|||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||
Optional<GroupDatabase.GroupRecord> groupRecord = groupDatabase.getGroup(groupId);
|
||||
|
||||
if (groupRecord.isPresent() && groupRecord.get().getAvatar() != null) {
|
||||
return new ByteArrayInputStream(groupRecord.get().getAvatar());
|
||||
if (!groupRecord.isPresent() || !AvatarHelper.hasAvatar(context, groupRecord.get().getRecipientId())) {
|
||||
throw new IOException("No avatar for group: " + groupId);
|
||||
}
|
||||
|
||||
throw new IOException("Couldn't load avatar for group: " + groupId);
|
||||
return AvatarHelper.getAvatar(context, groupRecord.get().getRecipientId());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -31,13 +31,12 @@ public class ProfileContactPhoto implements ContactPhoto {
|
|||
|
||||
@Override
|
||||
public @NonNull InputStream openInputStream(Context context) throws IOException {
|
||||
return AvatarHelper.getInputStreamFor(context, recipient.getId());
|
||||
return AvatarHelper.getAvatar(context, recipient.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Uri getUri(@NonNull Context context) {
|
||||
File avatarFile = AvatarHelper.getAvatarFile(context, recipient.getId());
|
||||
return avatarFile.exists() ? Uri.fromFile(avatarFile) : null;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -72,12 +71,6 @@ public class ProfileContactPhoto implements ContactPhoto {
|
|||
return 0;
|
||||
}
|
||||
|
||||
File avatarFile = AvatarHelper.getAvatarFile(ApplicationDependencies.getApplication(), recipient.getId());
|
||||
|
||||
if (avatarFile.exists()) {
|
||||
return avatarFile.lastModified();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return AvatarHelper.getLastModified(ApplicationDependencies.getApplication(), recipient.getId());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,4 +54,7 @@ public class ModernEncryptingPartOutputStream {
|
|||
}
|
||||
}
|
||||
|
||||
public static long getPlaintextLength(long cipherTextLength) {
|
||||
return cipherTextLength - 32;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,6 @@ public class GroupDatabase extends Database {
|
|||
static final String RECIPIENT_ID = "recipient_id";
|
||||
private static final String TITLE = "title";
|
||||
private static final String MEMBERS = "members";
|
||||
private static final String AVATAR = "avatar";
|
||||
private static final String AVATAR_ID = "avatar_id";
|
||||
private static final String AVATAR_KEY = "avatar_key";
|
||||
private static final String AVATAR_CONTENT_TYPE = "avatar_content_type";
|
||||
|
@ -59,7 +58,6 @@ public class GroupDatabase extends Database {
|
|||
RECIPIENT_ID + " INTEGER, " +
|
||||
TITLE + " TEXT, " +
|
||||
MEMBERS + " TEXT, " +
|
||||
AVATAR + " BLOB, " +
|
||||
AVATAR_ID + " INTEGER, " +
|
||||
AVATAR_KEY + " BLOB, " +
|
||||
AVATAR_CONTENT_TYPE + " TEXT, " +
|
||||
|
@ -75,7 +73,7 @@ public class GroupDatabase extends Database {
|
|||
};
|
||||
|
||||
private static final String[] GROUP_PROJECTION = {
|
||||
GROUP_ID, RECIPIENT_ID, TITLE, MEMBERS, AVATAR, AVATAR_ID, AVATAR_KEY, AVATAR_CONTENT_TYPE, AVATAR_RELAY, AVATAR_DIGEST,
|
||||
GROUP_ID, RECIPIENT_ID, TITLE, MEMBERS, AVATAR_ID, AVATAR_KEY, AVATAR_CONTENT_TYPE, AVATAR_RELAY, AVATAR_DIGEST,
|
||||
TIMESTAMP, ACTIVE, MMS
|
||||
};
|
||||
|
||||
|
@ -120,7 +118,7 @@ public class GroupDatabase extends Database {
|
|||
return true;
|
||||
}
|
||||
|
||||
boolean noMetadata = group.get().getAvatar() == null && TextUtils.isEmpty(group.get().getTitle());
|
||||
boolean noMetadata = !group.get().hasAvatar() && TextUtils.isEmpty(group.get().getTitle());
|
||||
boolean noMembers = group.get().getMembers().isEmpty() || (group.get().getMembers().size() == 1 && group.get().getMembers().contains(Recipient.self().getId()));
|
||||
|
||||
return noMetadata && noMembers;
|
||||
|
@ -228,6 +226,8 @@ public class GroupDatabase extends Database {
|
|||
contentValues.put(AVATAR_KEY, avatar.getKey());
|
||||
contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType());
|
||||
contentValues.put(AVATAR_DIGEST, avatar.getDigest().orNull());
|
||||
} else {
|
||||
contentValues.put(AVATAR_ID, 0);
|
||||
}
|
||||
|
||||
contentValues.put(AVATAR_RELAY, relay);
|
||||
|
@ -252,6 +252,8 @@ public class GroupDatabase extends Database {
|
|||
contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType());
|
||||
contentValues.put(AVATAR_KEY, avatar.getKey());
|
||||
contentValues.put(AVATAR_DIGEST, avatar.getDigest().orNull());
|
||||
} else {
|
||||
contentValues.put(AVATAR_ID, 0);
|
||||
}
|
||||
|
||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues,
|
||||
|
@ -274,20 +276,12 @@ public class GroupDatabase extends Database {
|
|||
Recipient.live(groupRecipient).refresh();
|
||||
}
|
||||
|
||||
public void updateAvatar(@NonNull GroupId groupId, @Nullable Bitmap avatar) {
|
||||
updateAvatar(groupId, BitmapUtil.toByteArray(avatar));
|
||||
}
|
||||
|
||||
public void updateAvatar(@NonNull GroupId groupId, @Nullable byte[] avatar) {
|
||||
long avatarId;
|
||||
|
||||
if (avatar != null) avatarId = Math.abs(new SecureRandom().nextLong());
|
||||
else avatarId = 0;
|
||||
|
||||
|
||||
ContentValues contentValues = new ContentValues(2);
|
||||
contentValues.put(AVATAR, avatar);
|
||||
contentValues.put(AVATAR_ID, avatarId);
|
||||
/**
|
||||
* Used to bust the Glide cache when an avatar changes.
|
||||
*/
|
||||
public void onAvatarUpdated(@NonNull GroupId groupId, boolean hasAvatar) {
|
||||
ContentValues contentValues = new ContentValues(1);
|
||||
contentValues.put(AVATAR_ID, hasAvatar ? Math.abs(new SecureRandom().nextLong()) : 0);
|
||||
|
||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
|
||||
new String[] {groupId.toString()});
|
||||
|
@ -388,7 +382,6 @@ public class GroupDatabase extends Database {
|
|||
RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID))),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(TITLE)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(MEMBERS)),
|
||||
cursor.getBlob(cursor.getColumnIndexOrThrow(AVATAR)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(AVATAR_ID)),
|
||||
cursor.getBlob(cursor.getColumnIndexOrThrow(AVATAR_KEY)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(AVATAR_CONTENT_TYPE)),
|
||||
|
@ -411,7 +404,6 @@ public class GroupDatabase extends Database {
|
|||
private final RecipientId recipientId;
|
||||
private final String title;
|
||||
private final List<RecipientId> members;
|
||||
private final byte[] avatar;
|
||||
private final long avatarId;
|
||||
private final byte[] avatarKey;
|
||||
private final byte[] avatarDigest;
|
||||
|
@ -420,14 +412,13 @@ public class GroupDatabase extends Database {
|
|||
private final boolean active;
|
||||
private final boolean mms;
|
||||
|
||||
public GroupRecord(@NonNull GroupId id, @NonNull RecipientId recipientId, String title, String members, byte[] avatar,
|
||||
public GroupRecord(@NonNull GroupId id, @NonNull RecipientId recipientId, String title, String members,
|
||||
long avatarId, byte[] avatarKey, String avatarContentType,
|
||||
String relay, boolean active, byte[] avatarDigest, boolean mms)
|
||||
{
|
||||
this.id = id;
|
||||
this.recipientId = recipientId;
|
||||
this.title = title;
|
||||
this.avatar = avatar;
|
||||
this.avatarId = avatarId;
|
||||
this.avatarKey = avatarKey;
|
||||
this.avatarDigest = avatarDigest;
|
||||
|
@ -456,8 +447,8 @@ public class GroupDatabase extends Database {
|
|||
return members;
|
||||
}
|
||||
|
||||
public byte[] getAvatar() {
|
||||
return avatar;
|
||||
public boolean hasAvatar() {
|
||||
return avatarId != 0;
|
||||
}
|
||||
|
||||
public long getAvatarId() {
|
||||
|
|
|
@ -21,7 +21,9 @@ import net.sqlcipher.database.SQLiteDatabase;
|
|||
import net.sqlcipher.database.SQLiteDatabaseHook;
|
||||
import net.sqlcipher.database.SQLiteOpenHelper;
|
||||
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
|
@ -51,14 +53,17 @@ import org.thoughtcrime.securesms.logging.Log;
|
|||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.FileUtils;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.SqlUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
|
@ -118,8 +123,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||
private static final int GROUPS_V2_RECIPIENT_CAPABILITY = 51;
|
||||
private static final int TRANSFER_FILE_CLEANUP = 52;
|
||||
private static final int PROFILE_DATA_MIGRATION = 53;
|
||||
private static final int AVATAR_LOCATION_MIGRATION = 54;
|
||||
|
||||
private static final int DATABASE_VERSION = 53;
|
||||
private static final int DATABASE_VERSION = 54;
|
||||
private static final String DATABASE_NAME = "signal.db";
|
||||
|
||||
private final Context context;
|
||||
|
@ -802,6 +808,49 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||
}
|
||||
}
|
||||
|
||||
if (oldVersion < AVATAR_LOCATION_MIGRATION) {
|
||||
File oldAvatarDirectory = new File(context.getFilesDir(), "avatars");
|
||||
File[] results = oldAvatarDirectory.listFiles();
|
||||
|
||||
if (results != null) {
|
||||
Log.i(TAG, "Preparing to migrate " + results.length + " avatars.");
|
||||
|
||||
for (File file : results) {
|
||||
if (Util.isLong(file.getName())) {
|
||||
try {
|
||||
AvatarHelper.setAvatar(context, RecipientId.from(file.getName()), new FileInputStream(file));
|
||||
} catch(IOException e) {
|
||||
Log.w(TAG, "Failed to copy file " + file.getName() + "! Skipping.");
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "Invalid avatar name '" + file.getName() + "'! Skipping.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "No avatar directory files found.");
|
||||
}
|
||||
|
||||
if (!FileUtils.deleteDirectory(oldAvatarDirectory)) {
|
||||
Log.w(TAG, "Failed to delete avatar directory.");
|
||||
}
|
||||
|
||||
try (Cursor cursor = db.rawQuery("SELECT recipient_id, avatar FROM groups", null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
RecipientId recipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow("recipient_id")));
|
||||
byte[] avatar = cursor.getBlob(cursor.getColumnIndexOrThrow("avatar"));
|
||||
|
||||
try {
|
||||
AvatarHelper.setAvatar(context, recipientId, avatar != null ? new ByteArrayInputStream(avatar) : null);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to copy avatar for " + recipientId + "! Skipping.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.execSQL("UPDATE groups SET avatar_id = 0 WHERE avatar IS NULL");
|
||||
db.execSQL("UPDATE groups SET avatar = NULL");
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
|
|
|
@ -19,7 +19,9 @@ import org.thoughtcrime.securesms.database.ThreadDatabase;
|
|||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupManager.GroupActionResult;
|
||||
import org.thoughtcrime.securesms.jobs.LeaveGroupJob;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
@ -32,6 +34,8 @@ import org.whispersystems.libsignal.util.guava.Optional;
|
|||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -39,6 +43,8 @@ import java.util.Set;
|
|||
|
||||
final class V1GroupManager {
|
||||
|
||||
private static final String TAG = Log.tag(V1GroupManager.class);
|
||||
|
||||
static @NonNull GroupActionResult createGroup(@NonNull Context context,
|
||||
@NonNull Set<RecipientId> memberIds,
|
||||
@Nullable Bitmap avatar,
|
||||
|
@ -55,7 +61,12 @@ final class V1GroupManager {
|
|||
groupDatabase.create(groupId, name, new LinkedList<>(memberIds), null, null);
|
||||
|
||||
if (!mms) {
|
||||
groupDatabase.updateAvatar(groupId, avatarBytes);
|
||||
try {
|
||||
AvatarHelper.setAvatar(context, groupRecipientId, avatarBytes != null ? new ByteArrayInputStream(avatarBytes) : null);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to save avatar!", e);
|
||||
}
|
||||
groupDatabase.onAvatarUpdated(groupId, avatarBytes != null);
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient.getId(), true);
|
||||
return sendGroupUpdate(context, groupId, memberIds, name, avatarBytes);
|
||||
} else {
|
||||
|
@ -71,18 +82,23 @@ final class V1GroupManager {
|
|||
@Nullable String name)
|
||||
throws InvalidNumberException
|
||||
{
|
||||
final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||
final byte[] avatarBytes = BitmapUtil.toByteArray(avatar);
|
||||
final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||
final RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId);
|
||||
final byte[] avatarBytes = BitmapUtil.toByteArray(avatar);
|
||||
|
||||
memberAddresses.add(Recipient.self().getId());
|
||||
groupDatabase.updateMembers(groupId, new LinkedList<>(memberAddresses));
|
||||
groupDatabase.updateTitle(groupId, name);
|
||||
groupDatabase.updateAvatar(groupId, avatarBytes);
|
||||
groupDatabase.onAvatarUpdated(groupId, avatarBytes != null);
|
||||
|
||||
if (!groupId.isMmsGroup()) {
|
||||
try {
|
||||
AvatarHelper.setAvatar(context, groupRecipientId, avatarBytes != null ? new ByteArrayInputStream(avatarBytes) : null);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to save avatar!", e);
|
||||
}
|
||||
return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes);
|
||||
} else {
|
||||
RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId);
|
||||
Recipient groupRecipient = Recipient.resolved(groupRecipientId);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
|
||||
return new GroupActionResult(groupRecipient, threadId);
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.jobmanager.Job;
|
|||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.AttachmentStreamUriLoader.AttachmentModel;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.Hex;
|
||||
|
@ -90,13 +91,14 @@ public class AvatarDownloadJob extends BaseJob {
|
|||
|
||||
SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver();
|
||||
SignalServiceAttachmentPointer pointer = new SignalServiceAttachmentPointer(avatarId, contentType, key, Optional.of(0), Optional.absent(), 0, 0, digest, fileName, false, Optional.absent(), Optional.absent());
|
||||
InputStream inputStream = receiver.retrieveAttachment(pointer, attachment, MAX_AVATAR_SIZE);
|
||||
Bitmap avatar = BitmapUtil.createScaledBitmap(context, new AttachmentModel(attachment, key, 0, digest), 500, 500);
|
||||
InputStream inputStream = receiver.retrieveAttachment(pointer, attachment, AvatarHelper.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE);
|
||||
|
||||
AvatarHelper.setAvatar(context, record.get().getRecipientId(), inputStream);
|
||||
DatabaseFactory.getGroupDatabase(context).onAvatarUpdated(groupId, true);
|
||||
|
||||
database.updateAvatar(groupId, avatar);
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (BitmapDecodingException | NonSuccessfulResponseCodeException | InvalidMessageException e) {
|
||||
} catch (NonSuccessfulResponseCodeException | InvalidMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
} finally {
|
||||
if (attachment != null)
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.jobmanager.Data;
|
|||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
|
@ -100,7 +101,7 @@ public class MultiDeviceGroupUpdateJob extends BaseJob {
|
|||
out.write(new DeviceGroup(record.getId().getDecodedId(),
|
||||
Optional.fromNullable(record.getTitle()),
|
||||
members,
|
||||
getAvatar(record.getAvatar()),
|
||||
getAvatar(record.getRecipientId()),
|
||||
record.isActive(),
|
||||
expirationTimer,
|
||||
Optional.of(recipient.getColor().serialize()),
|
||||
|
@ -151,13 +152,13 @@ public class MultiDeviceGroupUpdateJob extends BaseJob {
|
|||
}
|
||||
|
||||
|
||||
private Optional<SignalServiceAttachmentStream> getAvatar(@Nullable byte[] avatar) {
|
||||
if (avatar == null) return Optional.absent();
|
||||
private Optional<SignalServiceAttachmentStream> getAvatar(@NonNull RecipientId recipientId) throws IOException {
|
||||
if (!AvatarHelper.hasAvatar(context, recipientId)) return Optional.absent();
|
||||
|
||||
return Optional.of(SignalServiceAttachment.newStreamBuilder()
|
||||
.withStream(new ByteArrayInputStream(avatar))
|
||||
.withStream(AvatarHelper.getAvatar(context, recipientId))
|
||||
.withContentType("image/*")
|
||||
.withLength(avatar.length)
|
||||
.withLength(AvatarHelper.getAvatarLength(context, recipientId))
|
||||
.build());
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.jobmanager.Data;
|
|||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
|
@ -80,16 +81,16 @@ public class PushGroupUpdateJob extends BaseJob {
|
|||
Optional<GroupRecord> record = groupDatabase.getGroup(groupId);
|
||||
SignalServiceAttachment avatar = null;
|
||||
|
||||
if (record == null) {
|
||||
if (record == null || !record.isPresent()) {
|
||||
Log.w(TAG, "No information for group record info request: " + groupId.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (record.get().getAvatar() != null) {
|
||||
if (AvatarHelper.hasAvatar(context, record.get().getRecipientId())) {
|
||||
avatar = SignalServiceAttachmentStream.newStreamBuilder()
|
||||
.withContentType("image/jpeg")
|
||||
.withStream(new ByteArrayInputStream(record.get().getAvatar()))
|
||||
.withLength(record.get().getAvatar().length)
|
||||
.withStream(AvatarHelper.getAvatar(context, record.get().getRecipientId()))
|
||||
.withLength(AvatarHelper.getAvatarLength(context, record.get().getRecipientId()))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -97,17 +97,10 @@ public class RetrieveProfileAvatarJob extends BaseJob {
|
|||
File downloadDestination = File.createTempFile("avatar", "jpg", context.getCacheDir());
|
||||
|
||||
try {
|
||||
SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver();
|
||||
InputStream avatarStream = receiver.retrieveProfileAvatar(profileAvatar, downloadDestination, profileKey, MAX_PROFILE_SIZE_BYTES);
|
||||
File decryptDestination = File.createTempFile("avatar", "jpg", context.getCacheDir());
|
||||
SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver();
|
||||
InputStream avatarStream = receiver.retrieveProfileAvatar(profileAvatar, downloadDestination, profileKey, AvatarHelper.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE);
|
||||
|
||||
try {
|
||||
Util.copy(avatarStream, new FileOutputStream(decryptDestination));
|
||||
} catch (AssertionError e) {
|
||||
throw new IOException("Failed to copy stream. Likely a Conscrypt issue.", e);
|
||||
}
|
||||
|
||||
decryptDestination.renameTo(AvatarHelper.getAvatarFile(context, recipient.getId()));
|
||||
AvatarHelper.setAvatar(context, recipient.getId(), avatarStream);
|
||||
} catch (PushNetworkException e) {
|
||||
if (e.getCause() instanceof NonSuccessfulResponseCodeException) {
|
||||
Log.w(TAG, "Removing profile avatar (no image available) for: " + recipient.getId().serialize());
|
||||
|
|
|
@ -17,6 +17,7 @@ import androidx.lifecycle.ViewModelProviders;
|
|||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.TransportOptions;
|
||||
import org.thoughtcrime.securesms.imageeditor.model.EditorModel;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.scribbles.ImageEditorFragment;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
|
@ -27,7 +28,7 @@ import java.util.Collections;
|
|||
|
||||
public class AvatarSelectionActivity extends AppCompatActivity implements CameraFragment.Controller, ImageEditorFragment.Controller, MediaPickerFolderFragment.Controller, MediaPickerItemFragment.Controller {
|
||||
|
||||
private static final Point AVATAR_DIMENSIONS = new Point(1024, 1024);
|
||||
private static final Point AVATAR_DIMENSIONS = new Point(AvatarHelper.AVATAR_DIMENSIONS, AvatarHelper.AVATAR_DIMENSIONS);
|
||||
|
||||
private static final String IMAGE_CAPTURE = "IMAGE_CAPTURE";
|
||||
private static final String IMAGE_EDITOR = "IMAGE_EDITOR";
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
|||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
@ -64,7 +65,7 @@ public class AvatarMigrationJob extends MigrationJob {
|
|||
Recipient recipient = Recipient.external(context, file.getName());
|
||||
byte[] data = Util.readFully(new FileInputStream(file));
|
||||
|
||||
AvatarHelper.setAvatar(context, recipient.getId(), data);
|
||||
AvatarHelper.setAvatar(context, recipient.getId(), new ByteArrayInputStream(data));
|
||||
} else {
|
||||
Log.w(TAG, "Invalid file name! Can't migrate this file. It'll just get deleted.");
|
||||
}
|
||||
|
|
|
@ -6,81 +6,191 @@ import android.content.Context;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.ByteUnit;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class AvatarHelper {
|
||||
|
||||
private static final String TAG = Log.tag(AvatarHelper.class);
|
||||
|
||||
public static int AVATAR_DIMENSIONS = 1024;
|
||||
public static long AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE = ByteUnit.MEGABYTES.toBytes(10);
|
||||
|
||||
private static final String AVATAR_DIRECTORY = "avatars";
|
||||
|
||||
public static InputStream getInputStreamFor(@NonNull Context context, @NonNull RecipientId recipientId)
|
||||
throws IOException
|
||||
{
|
||||
return new FileInputStream(getAvatarFile(context, recipientId));
|
||||
}
|
||||
|
||||
public static List<File> getAvatarFiles(@NonNull Context context) {
|
||||
File avatarDirectory = new File(context.getFilesDir(), AVATAR_DIRECTORY);
|
||||
/**
|
||||
* Retrieves an iterable set of avatars. Only intended to be used during backup.
|
||||
*/
|
||||
public static Iterable<Avatar> getAvatars(@NonNull Context context) {
|
||||
File avatarDirectory = context.getDir(AVATAR_DIRECTORY, Context.MODE_PRIVATE);
|
||||
File[] results = avatarDirectory.listFiles();
|
||||
|
||||
if (results == null) return new LinkedList<>();
|
||||
else return Stream.of(results).toList();
|
||||
if (results == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return () -> {
|
||||
return new Iterator<Avatar>() {
|
||||
int i = 0;
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return i < results.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Avatar next() {
|
||||
File file = results[i];
|
||||
try {
|
||||
return new Avatar(getAvatar(context, RecipientId.from(file.getName())),
|
||||
file.getName(),
|
||||
ModernEncryptingPartOutputStream.getPlaintextLength(file.length()));
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
} finally {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes and avatar.
|
||||
*/
|
||||
public static void delete(@NonNull Context context, @NonNull RecipientId recipientId) {
|
||||
getAvatarFile(context, recipientId).delete();
|
||||
}
|
||||
|
||||
public static @NonNull File getAvatarFile(@NonNull Context context, @NonNull RecipientId recipientId) {
|
||||
File avatarDirectory = new File(context.getFilesDir(), AVATAR_DIRECTORY);
|
||||
avatarDirectory.mkdirs();
|
||||
|
||||
return new File(avatarDirectory, new File(recipientId.serialize()).getName());
|
||||
/**
|
||||
* Whether or not an avatar is present for the given recipient.
|
||||
*/
|
||||
public static boolean hasAvatar(@NonNull Context context, @NonNull RecipientId recipientId) {
|
||||
return getAvatarFile(context, recipientId).exists();
|
||||
}
|
||||
|
||||
public static void setAvatar(@NonNull Context context, @NonNull RecipientId recipientId, @Nullable byte[] data)
|
||||
throws IOException
|
||||
/**
|
||||
* Retrieves a stream for an avatar. If there is no avatar, the stream will likely throw an
|
||||
* IOException. It is recommended to call {@link #hasAvatar(Context, RecipientId)} first.
|
||||
*/
|
||||
public static @NonNull InputStream getAvatar(@NonNull Context context, @NonNull RecipientId recipientId) throws IOException {
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
File avatarFile = getAvatarFile(context, recipientId);
|
||||
|
||||
return ModernDecryptingPartInputStream.createFor(attachmentSecret, avatarFile, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the avatar on disk.
|
||||
*/
|
||||
public static long getAvatarLength(@NonNull Context context, @NonNull RecipientId recipientId) {
|
||||
return ModernEncryptingPartOutputStream.getPlaintextLength(getAvatarFile(context, recipientId).length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the contents of the input stream as the avatar for the specified recipient. If you pass
|
||||
* in null for the stream, the avatar will be deleted.
|
||||
*/
|
||||
public static void setAvatar(@NonNull Context context, @NonNull RecipientId recipientId, @Nullable InputStream inputStream)
|
||||
throws IOException
|
||||
{
|
||||
if (data == null) {
|
||||
if (inputStream == null) {
|
||||
delete(context, recipientId);
|
||||
} else {
|
||||
FileOutputStream out = new FileOutputStream(getAvatarFile(context, recipientId));
|
||||
out.write(data);
|
||||
out.close();
|
||||
return;
|
||||
}
|
||||
|
||||
OutputStream outputStream = null;
|
||||
try {
|
||||
outputStream = getOutputStream(context, recipientId);
|
||||
Util.copy(inputStream, outputStream);
|
||||
} finally {
|
||||
Util.close(outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
public static @NonNull StreamDetails avatarStream(@NonNull byte[] data) {
|
||||
return new StreamDetails(new ByteArrayInputStream(data), MediaUtil.IMAGE_JPEG, data.length);
|
||||
/**
|
||||
* Retrieves an output stream you can write to that will be saved as the avatar for the specified
|
||||
* recipient. Only intended to be used for backup. Otherwise, use {@link #setAvatar(Context, RecipientId, InputStream)}.
|
||||
*/
|
||||
public static @NonNull OutputStream getOutputStream(@NonNull Context context, @NonNull RecipientId recipientId) throws IOException {
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
File targetFile = getAvatarFile(context, recipientId);
|
||||
return ModernEncryptingPartOutputStream.createFor(attachmentSecret, targetFile, true).second;
|
||||
}
|
||||
|
||||
public static @Nullable StreamDetails getSelfProfileAvatarStream(@NonNull Context context) {
|
||||
File avatarFile = getAvatarFile(context, Recipient.self().getId());
|
||||
/**
|
||||
* Returns the timestamp of when the avatar was last modified, or zero if the avatar doesn't exist.
|
||||
*/
|
||||
public static long getLastModified(@NonNull Context context, @NonNull RecipientId recipientId) {
|
||||
File file = getAvatarFile(context, recipientId);
|
||||
|
||||
if (avatarFile.exists() && avatarFile.length() > 0) {
|
||||
try {
|
||||
FileInputStream stream = new FileInputStream(avatarFile);
|
||||
|
||||
return new StreamDetails(stream, MediaUtil.IMAGE_JPEG, avatarFile.length());
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
if (file.exists()) {
|
||||
return file.lastModified();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link StreamDetails} for the local user's own avatar, or null if one does not exist.
|
||||
*/
|
||||
public static @Nullable StreamDetails getSelfProfileAvatarStream(@NonNull Context context) {
|
||||
RecipientId selfId = Recipient.self().getId();
|
||||
|
||||
if (!hasAvatar(context, selfId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
InputStream stream = getAvatar(context, selfId);
|
||||
return new StreamDetails(stream, MediaUtil.IMAGE_JPEG, getAvatarLength(context, selfId));
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to read own avatar!", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull File getAvatarFile(@NonNull Context context, @NonNull RecipientId recipientId) {
|
||||
File directory = context.getDir(AVATAR_DIRECTORY, Context.MODE_PRIVATE);
|
||||
return new File(directory, recipientId.serialize());
|
||||
}
|
||||
|
||||
public static class Avatar {
|
||||
private final InputStream inputStream;
|
||||
private final String filename;
|
||||
private final long length;
|
||||
|
||||
public Avatar(@NonNull InputStream inputStream, @NonNull String filename, long length) {
|
||||
this.inputStream = inputStream;
|
||||
this.filename = filename;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public @NonNull InputStream getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
public @NonNull String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
public long getLength() {
|
||||
return length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
|||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
@ -76,10 +77,10 @@ class EditProfileRepository {
|
|||
void getCurrentAvatar(@NonNull Consumer<byte[]> avatarConsumer) {
|
||||
RecipientId selfId = Recipient.self().getId();
|
||||
|
||||
if (AvatarHelper.getAvatarFile(context, selfId).exists() && AvatarHelper.getAvatarFile(context, selfId).length() > 0) {
|
||||
if (AvatarHelper.hasAvatar(context, selfId)) {
|
||||
SimpleTask.run(() -> {
|
||||
try {
|
||||
return Util.readFully(AvatarHelper.getInputStreamFor(context, selfId));
|
||||
return Util.readFully(AvatarHelper.getAvatar(context, selfId));
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
|
@ -106,7 +107,7 @@ class EditProfileRepository {
|
|||
DatabaseFactory.getRecipientDatabase(context).setProfileName(Recipient.self().getId(), profileName);
|
||||
|
||||
try {
|
||||
AvatarHelper.setAvatar(context, Recipient.self().getId(), avatar);
|
||||
AvatarHelper.setAvatar(context, Recipient.self().getId(), avatar != null ? new ByteArrayInputStream(avatar) : null);
|
||||
} catch (IOException e) {
|
||||
return UploadResult.ERROR_FILE_IO;
|
||||
}
|
||||
|
|
|
@ -201,7 +201,7 @@ public final class LiveRecipient {
|
|||
title = unnamedGroupName;
|
||||
}
|
||||
|
||||
if (groupRecord.get().getAvatar() != null && groupRecord.get().getAvatar().length > 0) {
|
||||
if (groupRecord.get().hasAvatar()) {
|
||||
avatarId = Optional.of(groupRecord.get().getAvatarId());
|
||||
}
|
||||
|
||||
|
|
|
@ -49,13 +49,13 @@ public final class FileUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static void deleteDirectory(@Nullable File directory) {
|
||||
public static boolean deleteDirectory(@Nullable File directory) {
|
||||
if (directory == null || !directory.exists() || !directory.isDirectory()) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
deleteDirectoryContents(directory);
|
||||
|
||||
directory.delete();
|
||||
return directory.delete();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -224,7 +224,9 @@ public class Util {
|
|||
}
|
||||
}
|
||||
|
||||
public static void close(Closeable closeable) {
|
||||
public static void close(@Nullable Closeable closeable) {
|
||||
if (closeable == null) return;
|
||||
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (IOException e) {
|
||||
|
@ -607,4 +609,12 @@ public class Util {
|
|||
return concat;
|
||||
}
|
||||
|
||||
public static boolean isLong(String value) {
|
||||
try {
|
||||
Long.parseLong(value);
|
||||
return true;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@ public class SignalServiceMessageReceiver {
|
|||
* @throws IOException
|
||||
* @throws InvalidMessageException
|
||||
*/
|
||||
public InputStream retrieveAttachment(SignalServiceAttachmentPointer pointer, File destination, int maxSizeBytes)
|
||||
public InputStream retrieveAttachment(SignalServiceAttachmentPointer pointer, File destination, long maxSizeBytes)
|
||||
throws IOException, InvalidMessageException
|
||||
{
|
||||
return retrieveAttachment(pointer, destination, maxSizeBytes, null);
|
||||
|
@ -142,7 +142,7 @@ public class SignalServiceMessageReceiver {
|
|||
return socket.retrieveProfileByUsername(username, unidentifiedAccess);
|
||||
}
|
||||
|
||||
public InputStream retrieveProfileAvatar(String path, File destination, ProfileKey profileKey, int maxSizeBytes)
|
||||
public InputStream retrieveProfileAvatar(String path, File destination, ProfileKey profileKey, long maxSizeBytes)
|
||||
throws IOException
|
||||
{
|
||||
socket.retrieveProfileAvatar(path, destination, maxSizeBytes);
|
||||
|
@ -162,7 +162,7 @@ public class SignalServiceMessageReceiver {
|
|||
* @throws IOException
|
||||
* @throws InvalidMessageException
|
||||
*/
|
||||
public InputStream retrieveAttachment(SignalServiceAttachmentPointer pointer, File destination, int maxSizeBytes, ProgressListener listener)
|
||||
public InputStream retrieveAttachment(SignalServiceAttachmentPointer pointer, File destination, long maxSizeBytes, ProgressListener listener)
|
||||
throws IOException, InvalidMessageException
|
||||
{
|
||||
if (!pointer.getDigest().isPresent()) throw new InvalidMessageException("No attachment digest!");
|
||||
|
|
|
@ -498,7 +498,7 @@ public class PushServiceSocket {
|
|||
makeServiceRequest(SIGNED_PREKEY_PATH, "PUT", JsonUtil.toJson(signedPreKeyEntity));
|
||||
}
|
||||
|
||||
public void retrieveAttachment(long attachmentId, File destination, int maxSizeBytes, ProgressListener listener)
|
||||
public void retrieveAttachment(long attachmentId, File destination, long maxSizeBytes, ProgressListener listener)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException
|
||||
{
|
||||
downloadFromCdn(destination, String.format(Locale.US, ATTACHMENT_DOWNLOAD_PATH, attachmentId), maxSizeBytes, listener);
|
||||
|
@ -590,7 +590,7 @@ public class PushServiceSocket {
|
|||
}
|
||||
}
|
||||
|
||||
public void retrieveProfileAvatar(String path, File destination, int maxSizeBytes)
|
||||
public void retrieveProfileAvatar(String path, File destination, long maxSizeBytes)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException
|
||||
{
|
||||
downloadFromCdn(destination, path, maxSizeBytes, null);
|
||||
|
@ -874,7 +874,7 @@ public class PushServiceSocket {
|
|||
return new Pair<>(id, digest);
|
||||
}
|
||||
|
||||
private void downloadFromCdn(File destination, String path, int maxSizeBytes, ProgressListener listener)
|
||||
private void downloadFromCdn(File destination, String path, long maxSizeBytes, ProgressListener listener)
|
||||
throws PushNetworkException, NonSuccessfulResponseCodeException
|
||||
{
|
||||
try (FileOutputStream outputStream = new FileOutputStream(destination, true)) {
|
||||
|
@ -884,7 +884,7 @@ public class PushServiceSocket {
|
|||
}
|
||||
}
|
||||
|
||||
private void downloadFromCdn(OutputStream outputStream, long offset, String path, int maxSizeBytes, ProgressListener listener)
|
||||
private void downloadFromCdn(OutputStream outputStream, long offset, String path, long maxSizeBytes, ProgressListener listener)
|
||||
throws PushNetworkException, NonSuccessfulResponseCodeException
|
||||
{
|
||||
ConnectionHolder connectionHolder = getRandom(cdnClients, random);
|
||||
|
|
Loading…
Add table
Reference in a new issue