Ammar Githam
4 years ago
13 changed files with 2300 additions and 2628 deletions
-
2app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java
-
411app/src/main/java/awais/instagrabber/managers/DirectMessagesManager.kt
-
583app/src/main/java/awais/instagrabber/managers/InboxManager.kt
-
1880app/src/main/java/awais/instagrabber/managers/ThreadManager.java
-
1675app/src/main/java/awais/instagrabber/managers/ThreadManager.kt
-
2app/src/main/java/awais/instagrabber/services/DMSyncService.java
-
193app/src/main/java/awais/instagrabber/utils/MediaUploader.java
-
155app/src/main/java/awais/instagrabber/utils/MediaUploader.kt
-
2app/src/main/java/awais/instagrabber/viewmodels/DirectInboxViewModel.java
-
2app/src/main/java/awais/instagrabber/viewmodels/DirectPendingInboxViewModel.java
-
2app/src/main/java/awais/instagrabber/viewmodels/DirectSettingsViewModel.java
-
13app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java
-
4app/src/main/java/awais/instagrabber/viewmodels/PostViewV2ViewModel.java
@ -1,275 +1,222 @@ |
|||
package awais.instagrabber.managers; |
|||
|
|||
import android.content.ContentResolver; |
|||
import android.util.Log; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.lifecycle.LiveData; |
|||
import androidx.lifecycle.MutableLiveData; |
|||
|
|||
import com.google.common.collect.Iterables; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
import java.util.Locale; |
|||
import java.util.Set; |
|||
import java.util.UUID; |
|||
import java.util.function.Function; |
|||
|
|||
import awais.instagrabber.models.Resource; |
|||
import awais.instagrabber.repositories.requests.directmessages.ThreadIdOrUserIds; |
|||
import awais.instagrabber.repositories.responses.User; |
|||
import awais.instagrabber.repositories.responses.directmessages.DirectItem; |
|||
import awais.instagrabber.repositories.responses.directmessages.DirectThread; |
|||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse; |
|||
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.CookieUtils; |
|||
import awais.instagrabber.webservices.DirectMessagesService; |
|||
import retrofit2.Call; |
|||
import retrofit2.Callback; |
|||
import retrofit2.Response; |
|||
|
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public final class DirectMessagesManager { |
|||
private static final String TAG = DirectMessagesManager.class.getSimpleName(); |
|||
private static final Object LOCK = new Object(); |
|||
|
|||
private static DirectMessagesManager instance; |
|||
|
|||
private final InboxManager inboxManager; |
|||
private final InboxManager pendingInboxManager; |
|||
|
|||
private DirectMessagesService service; |
|||
|
|||
public static DirectMessagesManager getInstance() { |
|||
if (instance == null) { |
|||
synchronized (LOCK) { |
|||
if (instance == null) { |
|||
instance = new DirectMessagesManager(); |
|||
} |
|||
} |
|||
} |
|||
return instance; |
|||
} |
|||
|
|||
private DirectMessagesManager() { |
|||
inboxManager = InboxManager.getInstance(false); |
|||
pendingInboxManager = InboxManager.getInstance(true); |
|||
final String cookie = settingsHelper.getString(Constants.COOKIE); |
|||
final long viewerId = CookieUtils.getUserIdFromCookie(cookie); |
|||
final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID); |
|||
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); |
|||
if (csrfToken == null) return; |
|||
service = DirectMessagesService.getInstance(csrfToken, viewerId, deviceUuid); |
|||
} |
|||
|
|||
public void moveThreadFromPending(@NonNull final String threadId) { |
|||
final List<DirectThread> pendingThreads = pendingInboxManager.getThreads().getValue(); |
|||
if (pendingThreads == null) return; |
|||
final int index = Iterables.indexOf(pendingThreads, t -> t != null && t.getThreadId().equals(threadId)); |
|||
if (index < 0) return; |
|||
final DirectThread thread = pendingThreads.get(index); |
|||
final DirectItem threadFirstDirectItem = thread.getFirstDirectItem(); |
|||
if (threadFirstDirectItem == null) return; |
|||
final List<DirectThread> threads = inboxManager.getThreads().getValue(); |
|||
int insertIndex = 0; |
|||
package awais.instagrabber.managers |
|||
|
|||
import android.content.ContentResolver |
|||
import android.util.Log |
|||
import androidx.lifecycle.LiveData |
|||
import androidx.lifecycle.MutableLiveData |
|||
import awais.instagrabber.managers.ThreadManager.Companion.getInstance |
|||
import awais.instagrabber.models.Resource |
|||
import awais.instagrabber.models.Resource.Companion.error |
|||
import awais.instagrabber.models.Resource.Companion.loading |
|||
import awais.instagrabber.models.Resource.Companion.success |
|||
import awais.instagrabber.repositories.requests.directmessages.ThreadIdOrUserIds.Companion.of |
|||
import awais.instagrabber.repositories.responses.User |
|||
import awais.instagrabber.repositories.responses.directmessages.DirectThread |
|||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse |
|||
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient |
|||
import awais.instagrabber.utils.Constants |
|||
import awais.instagrabber.utils.Utils |
|||
import awais.instagrabber.utils.getCsrfTokenFromCookie |
|||
import awais.instagrabber.utils.getUserIdFromCookie |
|||
import awais.instagrabber.webservices.DirectMessagesService |
|||
import awais.instagrabber.webservices.DirectMessagesService.Companion.getInstance |
|||
import retrofit2.Call |
|||
import retrofit2.Callback |
|||
import retrofit2.Response |
|||
import java.io.IOException |
|||
import java.util.* |
|||
|
|||
object DirectMessagesManager { |
|||
val inboxManager: InboxManager by lazy { InboxManager.getInstance(false) } |
|||
val pendingInboxManager: InboxManager by lazy { InboxManager.getInstance(true) } |
|||
|
|||
private val TAG = DirectMessagesManager::class.java.simpleName |
|||
private val viewerId: Long |
|||
private val deviceUuid: String |
|||
private val csrfToken: String |
|||
private val service: DirectMessagesService |
|||
|
|||
fun moveThreadFromPending(threadId: String) { |
|||
val pendingThreads = pendingInboxManager.threads.value ?: return |
|||
val index = pendingThreads.indexOfFirst { it.threadId == threadId } |
|||
if (index < 0) return |
|||
val thread = pendingThreads[index] |
|||
val threadFirstDirectItem = thread.firstDirectItem ?: return |
|||
val threads = inboxManager.threads.value |
|||
var insertIndex = 0 |
|||
if (threads != null) { |
|||
for (final DirectThread tempThread : threads) { |
|||
final DirectItem firstDirectItem = tempThread.getFirstDirectItem(); |
|||
if (firstDirectItem == null) continue; |
|||
final long timestamp = firstDirectItem.getTimestamp(); |
|||
for (tempThread in threads) { |
|||
val firstDirectItem = tempThread.firstDirectItem ?: continue |
|||
val timestamp = firstDirectItem.getTimestamp() |
|||
if (timestamp < threadFirstDirectItem.getTimestamp()) { |
|||
break; |
|||
break |
|||
} |
|||
insertIndex++; |
|||
insertIndex++ |
|||
} |
|||
} |
|||
thread.setPending(false); |
|||
inboxManager.addThread(thread, insertIndex); |
|||
pendingInboxManager.removeThread(threadId); |
|||
final Integer currentTotal = inboxManager.getPendingRequestsTotal().getValue(); |
|||
if (currentTotal == null) return; |
|||
inboxManager.setPendingRequestsTotal(currentTotal - 1); |
|||
} |
|||
|
|||
public InboxManager getInboxManager() { |
|||
return inboxManager; |
|||
thread.pending = false |
|||
inboxManager.addThread(thread, insertIndex) |
|||
pendingInboxManager.removeThread(threadId) |
|||
val currentTotal = inboxManager.getPendingRequestsTotal().value ?: return |
|||
inboxManager.setPendingRequestsTotal(currentTotal - 1) |
|||
} |
|||
|
|||
public InboxManager getPendingInboxManager() { |
|||
return pendingInboxManager; |
|||
fun getThreadManager( |
|||
threadId: String, |
|||
pending: Boolean, |
|||
currentUser: User, |
|||
contentResolver: ContentResolver, |
|||
): ThreadManager { |
|||
return getInstance(threadId, pending, currentUser, contentResolver, viewerId, csrfToken, deviceUuid) |
|||
} |
|||
|
|||
public ThreadManager getThreadManager(@NonNull final String threadId, |
|||
final boolean pending, |
|||
@NonNull final User currentUser, |
|||
@NonNull final ContentResolver contentResolver) { |
|||
return ThreadManager.getInstance(threadId, pending, currentUser, contentResolver); |
|||
} |
|||
|
|||
public void createThread(final long userPk, |
|||
@Nullable final Function<DirectThread, Void> callback) { |
|||
if (service == null) return; |
|||
final Call<DirectThread> createThreadRequest = service.createThread(Collections.singletonList(userPk), null); |
|||
createThreadRequest.enqueue(new Callback<DirectThread>() { |
|||
@Override |
|||
public void onResponse(@NonNull final Call<DirectThread> call, @NonNull final Response<DirectThread> response) { |
|||
if (!response.isSuccessful()) { |
|||
if (response.errorBody() != null) { |
|||
fun createThread( |
|||
userPk: Long, |
|||
callback: ((DirectThread) -> Unit)?, |
|||
) { |
|||
val createThreadRequest = service.createThread(listOf(userPk), null) |
|||
createThreadRequest.enqueue(object : Callback<DirectThread?> { |
|||
override fun onResponse(call: Call<DirectThread?>, response: Response<DirectThread?>) { |
|||
if (!response.isSuccessful) { |
|||
val errorBody = response.errorBody() |
|||
if (errorBody != null) { |
|||
try { |
|||
final String string = response.errorBody().string(); |
|||
final String msg = String.format(Locale.US, |
|||
"onResponse: url: %s, responseCode: %d, errorBody: %s", |
|||
call.request().url().toString(), |
|||
response.code(), |
|||
string); |
|||
Log.e(TAG, msg); |
|||
} catch (IOException e) { |
|||
Log.e(TAG, "onResponse: ", e); |
|||
val string = errorBody.string() |
|||
val msg = String.format(Locale.US, |
|||
"onResponse: url: %s, responseCode: %d, errorBody: %s", |
|||
call.request().url().toString(), |
|||
response.code(), |
|||
string) |
|||
Log.e(TAG, msg) |
|||
} catch (e: IOException) { |
|||
Log.e(TAG, "onResponse: ", e) |
|||
} |
|||
return; |
|||
return |
|||
} |
|||
Log.e(TAG, "onResponse: request was not successful and response error body was null"); |
|||
return; |
|||
Log.e(TAG, "onResponse: request was not successful and response error body was null") |
|||
return |
|||
} |
|||
final DirectThread thread = response.body(); |
|||
val thread = response.body() |
|||
if (thread == null) { |
|||
Log.e(TAG, "onResponse: thread is null"); |
|||
return; |
|||
} |
|||
if (callback != null) { |
|||
callback.apply(thread); |
|||
Log.e(TAG, "onResponse: thread is null") |
|||
return |
|||
} |
|||
callback?.invoke(thread) |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(@NonNull final Call<DirectThread> call, @NonNull final Throwable t) { |
|||
|
|||
} |
|||
}); |
|||
override fun onFailure(call: Call<DirectThread?>, t: Throwable) {} |
|||
}) |
|||
} |
|||
|
|||
public void sendMedia(@NonNull final Set<RankedRecipient> recipients, final String mediaId) { |
|||
final int[] resultsCount = {0}; |
|||
final Function<Void, Void> callback = unused -> { |
|||
resultsCount[0]++; |
|||
if (resultsCount[0] == recipients.size()) { |
|||
inboxManager.refresh(); |
|||
fun sendMedia(recipients: Set<RankedRecipient>, mediaId: String) { |
|||
val resultsCount = intArrayOf(0) |
|||
val callback: () -> Unit = { |
|||
resultsCount[0]++ |
|||
if (resultsCount[0] == recipients.size) { |
|||
inboxManager.refresh() |
|||
} |
|||
return null; |
|||
}; |
|||
for (final RankedRecipient recipient : recipients) { |
|||
if (recipient == null) continue; |
|||
sendMedia(recipient, mediaId, false, callback); |
|||
} |
|||
for (recipient in recipients) { |
|||
sendMedia(recipient, mediaId, false, callback) |
|||
} |
|||
} |
|||
|
|||
public void sendMedia(@NonNull final RankedRecipient recipient, final String mediaId) { |
|||
sendMedia(recipient, mediaId, true, null); |
|||
fun sendMedia(recipient: RankedRecipient, mediaId: String) { |
|||
sendMedia(recipient, mediaId, true, null) |
|||
} |
|||
|
|||
private void sendMedia(@NonNull final RankedRecipient recipient, |
|||
@NonNull final String mediaId, |
|||
final boolean refreshInbox, |
|||
@Nullable final Function<Void, Void> callback) { |
|||
if (recipient.getThread() == null && recipient.getUser() != null) { |
|||
private fun sendMedia( |
|||
recipient: RankedRecipient, |
|||
mediaId: String, |
|||
refreshInbox: Boolean, |
|||
callback: (() -> Unit)?, |
|||
) { |
|||
if (recipient.thread == null && recipient.user != null) { |
|||
// create thread and forward |
|||
createThread(recipient.getUser().getPk(), directThread -> { |
|||
sendMedia(directThread, mediaId, unused -> { |
|||
createThread(recipient.user.pk) { (threadId) -> |
|||
val threadIdTemp = threadId ?: return@createThread |
|||
sendMedia(threadIdTemp, mediaId) { |
|||
if (refreshInbox) { |
|||
inboxManager.refresh(); |
|||
} |
|||
if (callback != null) { |
|||
callback.apply(null); |
|||
inboxManager.refresh() |
|||
} |
|||
return null; |
|||
}); |
|||
return null; |
|||
}); |
|||
callback?.invoke() |
|||
} |
|||
} |
|||
} |
|||
if (recipient.getThread() == null) return; |
|||
if (recipient.thread == null) return |
|||
// just forward |
|||
final DirectThread thread = recipient.getThread(); |
|||
sendMedia(thread, mediaId, unused -> { |
|||
val thread = recipient.thread |
|||
val threadId = thread.threadId ?: return |
|||
sendMedia(threadId, mediaId) { |
|||
if (refreshInbox) { |
|||
inboxManager.refresh(); |
|||
} |
|||
if (callback != null) { |
|||
callback.apply(null); |
|||
inboxManager.refresh() |
|||
} |
|||
return null; |
|||
}); |
|||
} |
|||
|
|||
@NonNull |
|||
public LiveData<Resource<Object>> sendMedia(@NonNull final DirectThread thread, |
|||
@NonNull final String mediaId, |
|||
@Nullable final Function<Void, Void> callback) { |
|||
return sendMedia(thread.getThreadId(), mediaId, callback); |
|||
callback?.invoke() |
|||
} |
|||
} |
|||
|
|||
@NonNull |
|||
public LiveData<Resource<Object>> sendMedia(@NonNull final String threadId, |
|||
@NonNull final String mediaId, |
|||
@Nullable final Function<Void, Void> callback) { |
|||
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); |
|||
data.postValue(Resource.loading(null)); |
|||
final Call<DirectThreadBroadcastResponse> request = service.broadcastMediaShare( |
|||
UUID.randomUUID().toString(), |
|||
ThreadIdOrUserIds.of(threadId), |
|||
mediaId |
|||
); |
|||
request.enqueue(new Callback<DirectThreadBroadcastResponse>() { |
|||
@Override |
|||
public void onResponse(@NonNull final Call<DirectThreadBroadcastResponse> call, |
|||
@NonNull final Response<DirectThreadBroadcastResponse> response) { |
|||
if (response.isSuccessful()) { |
|||
data.postValue(Resource.success(new Object())); |
|||
if (callback != null) { |
|||
callback.apply(null); |
|||
} |
|||
return; |
|||
private fun sendMedia( |
|||
threadId: String, |
|||
mediaId: String, |
|||
callback: (() -> Unit)?, |
|||
): LiveData<Resource<Any?>> { |
|||
val data = MutableLiveData<Resource<Any?>>() |
|||
data.postValue(loading(null)) |
|||
val request = service.broadcastMediaShare( |
|||
UUID.randomUUID().toString(), |
|||
of(threadId), |
|||
mediaId |
|||
) |
|||
request.enqueue(object : Callback<DirectThreadBroadcastResponse?> { |
|||
override fun onResponse( |
|||
call: Call<DirectThreadBroadcastResponse?>, |
|||
response: Response<DirectThreadBroadcastResponse?>, |
|||
) { |
|||
if (response.isSuccessful) { |
|||
data.postValue(success(Any())) |
|||
callback?.invoke() |
|||
return |
|||
} |
|||
if (response.errorBody() != null) { |
|||
val errorBody = response.errorBody() |
|||
if (errorBody != null) { |
|||
try { |
|||
final String string = response.errorBody().string(); |
|||
final String msg = String.format(Locale.US, |
|||
"onResponse: url: %s, responseCode: %d, errorBody: %s", |
|||
call.request().url().toString(), |
|||
response.code(), |
|||
string); |
|||
Log.e(TAG, msg); |
|||
data.postValue(Resource.error(msg, null)); |
|||
} catch (IOException e) { |
|||
Log.e(TAG, "onResponse: ", e); |
|||
data.postValue(Resource.error(e.getMessage(), null)); |
|||
} |
|||
if (callback != null) { |
|||
callback.apply(null); |
|||
val string = errorBody.string() |
|||
val msg = String.format(Locale.US, |
|||
"onResponse: url: %s, responseCode: %d, errorBody: %s", |
|||
call.request().url().toString(), |
|||
response.code(), |
|||
string) |
|||
Log.e(TAG, msg) |
|||
data.postValue(error(msg, null)) |
|||
} catch (e: IOException) { |
|||
Log.e(TAG, "onResponse: ", e) |
|||
data.postValue(error(e.message, null)) |
|||
} |
|||
return; |
|||
} |
|||
final String msg = "onResponse: request was not successful and response error body was null"; |
|||
Log.e(TAG, msg); |
|||
data.postValue(Resource.error(msg, null)); |
|||
if (callback != null) { |
|||
callback.apply(null); |
|||
callback?.invoke() |
|||
return |
|||
} |
|||
val msg = "onResponse: request was not successful and response error body was null" |
|||
Log.e(TAG, msg) |
|||
data.postValue(error(msg, null)) |
|||
callback?.invoke() |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(@NonNull final Call<DirectThreadBroadcastResponse> call, @NonNull final Throwable t) { |
|||
Log.e(TAG, "onFailure: ", t); |
|||
data.postValue(Resource.error(t.getMessage(), null)); |
|||
if (callback != null) { |
|||
callback.apply(null); |
|||
} |
|||
override fun onFailure(call: Call<DirectThreadBroadcastResponse?>, t: Throwable) { |
|||
Log.e(TAG, "onFailure: ", t) |
|||
data.postValue(error(t.message, null)) |
|||
callback?.invoke() |
|||
} |
|||
}); |
|||
return data; |
|||
}) |
|||
return data |
|||
} |
|||
|
|||
init { |
|||
val cookie = Utils.settingsHelper.getString(Constants.COOKIE) |
|||
viewerId = getUserIdFromCookie(cookie) |
|||
deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID) |
|||
val csrfToken = getCsrfTokenFromCookie(cookie) |
|||
require(!csrfToken.isNullOrBlank() && viewerId != 0L && deviceUuid.isNotBlank()) { "User is not logged in!" } |
|||
this.csrfToken = csrfToken |
|||
service = getInstance(csrfToken, viewerId, deviceUuid) |
|||
} |
|||
} |
@ -1,382 +1,349 @@ |
|||
package awais.instagrabber.managers; |
|||
|
|||
import android.util.Log; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.lifecycle.LiveData; |
|||
import androidx.lifecycle.MutableLiveData; |
|||
import androidx.lifecycle.Transformations; |
|||
|
|||
import com.google.common.cache.CacheBuilder; |
|||
import com.google.common.cache.CacheLoader; |
|||
import com.google.common.cache.LoadingCache; |
|||
import com.google.common.collect.ImmutableList; |
|||
import com.google.common.collect.Iterables; |
|||
|
|||
import java.util.Collection; |
|||
import java.util.Collections; |
|||
import java.util.Comparator; |
|||
import java.util.LinkedList; |
|||
import java.util.List; |
|||
import java.util.Objects; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.models.Resource; |
|||
import awais.instagrabber.repositories.responses.User; |
|||
import awais.instagrabber.repositories.responses.directmessages.DirectBadgeCount; |
|||
import awais.instagrabber.repositories.responses.directmessages.DirectInbox; |
|||
import awais.instagrabber.repositories.responses.directmessages.DirectInboxResponse; |
|||
import awais.instagrabber.repositories.responses.directmessages.DirectItem; |
|||
import awais.instagrabber.repositories.responses.directmessages.DirectThread; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.CookieUtils; |
|||
import awais.instagrabber.utils.TextUtils; |
|||
import awais.instagrabber.webservices.DirectMessagesService; |
|||
import retrofit2.Call; |
|||
import retrofit2.Callback; |
|||
import retrofit2.Response; |
|||
|
|||
import static androidx.lifecycle.Transformations.distinctUntilChanged; |
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public final class InboxManager { |
|||
private static final String TAG = InboxManager.class.getSimpleName(); |
|||
private static final LoadingCache<String, Object> THREAD_LOCKS = CacheBuilder |
|||
.newBuilder() |
|||
.expireAfterAccess(1, TimeUnit.MINUTES) // max lock time ever expected |
|||
.build(CacheLoader.from(Object::new)); |
|||
private static final Comparator<DirectThread> THREAD_COMPARATOR = (t1, t2) -> { |
|||
final DirectItem t1FirstDirectItem = t1.getFirstDirectItem(); |
|||
final DirectItem t2FirstDirectItem = t2.getFirstDirectItem(); |
|||
if (t1FirstDirectItem == null && t2FirstDirectItem == null) return 0; |
|||
if (t1FirstDirectItem == null) return 1; |
|||
if (t2FirstDirectItem == null) return -1; |
|||
return Long.compare(t2FirstDirectItem.getTimestamp(), t1FirstDirectItem.getTimestamp()); |
|||
}; |
|||
|
|||
private final MutableLiveData<Resource<DirectInbox>> inbox = new MutableLiveData<>(); |
|||
private final MutableLiveData<Resource<Integer>> unseenCount = new MutableLiveData<>(); |
|||
private final MutableLiveData<Integer> pendingRequestsTotal = new MutableLiveData<>(0); |
|||
|
|||
private final LiveData<List<DirectThread>> threads; |
|||
private final DirectMessagesService service; |
|||
private final boolean pending; |
|||
|
|||
private Call<DirectInboxResponse> inboxRequest; |
|||
private Call<DirectBadgeCount> unseenCountRequest; |
|||
private long seqId; |
|||
private String cursor; |
|||
private boolean hasOlder = true; |
|||
private User viewer; |
|||
|
|||
@NonNull |
|||
public static InboxManager getInstance(final boolean pending) { |
|||
return new InboxManager(pending); |
|||
} |
|||
|
|||
private InboxManager(final boolean pending) { |
|||
this.pending = pending; |
|||
final String cookie = settingsHelper.getString(Constants.COOKIE); |
|||
final long userId = CookieUtils.getUserIdFromCookie(cookie); |
|||
final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID); |
|||
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); |
|||
if (TextUtils.isEmpty(csrfToken)) { |
|||
throw new IllegalArgumentException("csrfToken is empty!"); |
|||
} else if (userId == 0) { |
|||
throw new IllegalArgumentException("user id invalid"); |
|||
} else if (TextUtils.isEmpty(deviceUuid)) { |
|||
throw new IllegalArgumentException("device uuid is empty!"); |
|||
} |
|||
service = DirectMessagesService.getInstance(csrfToken, userId, deviceUuid); |
|||
|
|||
// Transformations |
|||
threads = distinctUntilChanged(Transformations.map(inbox, inboxResource -> { |
|||
if (inboxResource == null) { |
|||
return Collections.emptyList(); |
|||
} |
|||
final DirectInbox inbox = inboxResource.data; |
|||
if (inbox == null) { |
|||
return Collections.emptyList(); |
|||
} |
|||
return ImmutableList.sortedCopyOf(THREAD_COMPARATOR, inbox.getThreads()); |
|||
})); |
|||
|
|||
fetchInbox(); |
|||
if (!pending) { |
|||
fetchUnseenCount(); |
|||
} |
|||
} |
|||
|
|||
public LiveData<Resource<DirectInbox>> getInbox() { |
|||
return distinctUntilChanged(inbox); |
|||
} |
|||
|
|||
public LiveData<List<DirectThread>> getThreads() { |
|||
return threads; |
|||
} |
|||
|
|||
public LiveData<Resource<Integer>> getUnseenCount() { |
|||
return distinctUntilChanged(unseenCount); |
|||
package awais.instagrabber.managers |
|||
|
|||
import android.util.Log |
|||
import androidx.lifecycle.LiveData |
|||
import androidx.lifecycle.MutableLiveData |
|||
import androidx.lifecycle.Transformations |
|||
import awais.instagrabber.R |
|||
import awais.instagrabber.models.Resource |
|||
import awais.instagrabber.models.Resource.Companion.error |
|||
import awais.instagrabber.models.Resource.Companion.loading |
|||
import awais.instagrabber.models.Resource.Companion.success |
|||
import awais.instagrabber.repositories.responses.User |
|||
import awais.instagrabber.repositories.responses.directmessages.* |
|||
import awais.instagrabber.utils.Constants |
|||
import awais.instagrabber.utils.Utils |
|||
import awais.instagrabber.utils.getCsrfTokenFromCookie |
|||
import awais.instagrabber.utils.getUserIdFromCookie |
|||
import awais.instagrabber.webservices.DirectMessagesService |
|||
import awais.instagrabber.webservices.DirectMessagesService.Companion.getInstance |
|||
import com.google.common.cache.CacheBuilder |
|||
import com.google.common.cache.CacheLoader |
|||
import com.google.common.collect.ImmutableList |
|||
import retrofit2.Call |
|||
import retrofit2.Callback |
|||
import retrofit2.Response |
|||
import java.util.* |
|||
import java.util.concurrent.TimeUnit |
|||
|
|||
class InboxManager private constructor(private val pending: Boolean) { |
|||
private val inbox = MutableLiveData<Resource<DirectInbox?>>() |
|||
private val unseenCount = MutableLiveData<Resource<Int?>>() |
|||
private val pendingRequestsTotal = MutableLiveData(0) |
|||
val threads: LiveData<List<DirectThread>> |
|||
private val service: DirectMessagesService |
|||
private var inboxRequest: Call<DirectInboxResponse?>? = null |
|||
private var unseenCountRequest: Call<DirectBadgeCount?>? = null |
|||
private var seqId: Long = 0 |
|||
private var cursor: String? = null |
|||
private var hasOlder = true |
|||
var viewer: User? = null |
|||
private set |
|||
|
|||
fun getInbox(): LiveData<Resource<DirectInbox?>> { |
|||
return Transformations.distinctUntilChanged(inbox) |
|||
} |
|||
|
|||
public LiveData<Integer> getPendingRequestsTotal() { |
|||
return distinctUntilChanged(pendingRequestsTotal); |
|||
fun getUnseenCount(): LiveData<Resource<Int?>> { |
|||
return Transformations.distinctUntilChanged(unseenCount) |
|||
} |
|||
|
|||
public User getViewer() { |
|||
return viewer; |
|||
fun getPendingRequestsTotal(): LiveData<Int> { |
|||
return Transformations.distinctUntilChanged(pendingRequestsTotal) |
|||
} |
|||
|
|||
public void fetchInbox() { |
|||
final Resource<DirectInbox> inboxResource = inbox.getValue(); |
|||
if ((inboxResource != null && inboxResource.status == Resource.Status.LOADING) || !hasOlder) return; |
|||
stopCurrentInboxRequest(); |
|||
inbox.postValue(Resource.loading(getCurrentDirectInbox())); |
|||
inboxRequest = pending ? service.fetchPendingInbox(cursor, seqId) : service.fetchInbox(cursor, seqId); |
|||
inboxRequest.enqueue(new Callback<DirectInboxResponse>() { |
|||
@Override |
|||
public void onResponse(@NonNull final Call<DirectInboxResponse> call, @NonNull final Response<DirectInboxResponse> response) { |
|||
parseInboxResponse(response.body()); |
|||
fun fetchInbox() { |
|||
val inboxResource = inbox.value |
|||
if (inboxResource != null && inboxResource.status === Resource.Status.LOADING || !hasOlder) return |
|||
stopCurrentInboxRequest() |
|||
inbox.postValue(loading(currentDirectInbox)) |
|||
inboxRequest = if (pending) service.fetchPendingInbox(cursor, seqId) else service.fetchInbox(cursor, seqId) |
|||
inboxRequest?.enqueue(object : Callback<DirectInboxResponse?> { |
|||
override fun onResponse(call: Call<DirectInboxResponse?>, response: Response<DirectInboxResponse?>) { |
|||
val body = response.body() |
|||
if (body == null) { |
|||
Log.e(TAG, "parseInboxResponse: Response is null") |
|||
inbox.postValue(error(R.string.generic_null_response, currentDirectInbox)) |
|||
hasOlder = false |
|||
return |
|||
} |
|||
parseInboxResponse(body) |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(@NonNull final Call<DirectInboxResponse> call, @NonNull final Throwable t) { |
|||
Log.e(TAG, "Failed fetching dm inbox", t); |
|||
inbox.postValue(Resource.error(t.getMessage(), getCurrentDirectInbox())); |
|||
hasOlder = false; |
|||
override fun onFailure(call: Call<DirectInboxResponse?>, t: Throwable) { |
|||
Log.e(TAG, "Failed fetching dm inbox", t) |
|||
inbox.postValue(error(t.message, currentDirectInbox)) |
|||
hasOlder = false |
|||
} |
|||
}); |
|||
}) |
|||
} |
|||
|
|||
public void fetchUnseenCount() { |
|||
final Resource<Integer> unseenCountResource = unseenCount.getValue(); |
|||
if ((unseenCountResource != null && unseenCountResource.status == Resource.Status.LOADING)) return; |
|||
stopCurrentUnseenCountRequest(); |
|||
unseenCount.postValue(Resource.loading(getCurrentUnseenCount())); |
|||
unseenCountRequest = service.fetchUnseenCount(); |
|||
unseenCountRequest.enqueue(new Callback<DirectBadgeCount>() { |
|||
@Override |
|||
public void onResponse(@NonNull final Call<DirectBadgeCount> call, @NonNull final Response<DirectBadgeCount> response) { |
|||
final DirectBadgeCount directBadgeCount = response.body(); |
|||
fun fetchUnseenCount() { |
|||
val unseenCountResource = unseenCount.value |
|||
if (unseenCountResource != null && unseenCountResource.status === Resource.Status.LOADING) return |
|||
stopCurrentUnseenCountRequest() |
|||
unseenCount.postValue(loading(currentUnseenCount)) |
|||
unseenCountRequest = service.fetchUnseenCount() |
|||
unseenCountRequest?.enqueue(object : Callback<DirectBadgeCount?> { |
|||
override fun onResponse(call: Call<DirectBadgeCount?>, response: Response<DirectBadgeCount?>) { |
|||
val directBadgeCount = response.body() |
|||
if (directBadgeCount == null) { |
|||
Log.e(TAG, "onResponse: directBadgeCount Response is null"); |
|||
unseenCount.postValue(Resource.error(R.string.dms_inbox_error_null_count, getCurrentUnseenCount())); |
|||
return; |
|||
Log.e(TAG, "onResponse: directBadgeCount Response is null") |
|||
unseenCount.postValue(error(R.string.dms_inbox_error_null_count, currentUnseenCount)) |
|||
return |
|||
} |
|||
unseenCount.postValue(Resource.success(directBadgeCount.getBadgeCount())); |
|||
unseenCount.postValue(success(directBadgeCount.badgeCount)) |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(@NonNull final Call<DirectBadgeCount> call, @NonNull final Throwable t) { |
|||
Log.e(TAG, "Failed fetching unseen count", t); |
|||
unseenCount.postValue(Resource.error(t.getMessage(), getCurrentUnseenCount())); |
|||
override fun onFailure(call: Call<DirectBadgeCount?>, t: Throwable) { |
|||
Log.e(TAG, "Failed fetching unseen count", t) |
|||
unseenCount.postValue(error(t.message, currentUnseenCount)) |
|||
} |
|||
}); |
|||
}) |
|||
} |
|||
|
|||
public void refresh() { |
|||
cursor = null; |
|||
seqId = 0; |
|||
hasOlder = true; |
|||
fetchInbox(); |
|||
fun refresh() { |
|||
cursor = null |
|||
seqId = 0 |
|||
hasOlder = true |
|||
fetchInbox() |
|||
if (!pending) { |
|||
fetchUnseenCount(); |
|||
fetchUnseenCount() |
|||
} |
|||
} |
|||
|
|||
private DirectInbox getCurrentDirectInbox() { |
|||
final Resource<DirectInbox> inboxResource = inbox.getValue(); |
|||
return inboxResource != null ? inboxResource.data : null; |
|||
} |
|||
|
|||
private void parseInboxResponse(final DirectInboxResponse response) { |
|||
if (response == null) { |
|||
Log.e(TAG, "parseInboxResponse: Response is null"); |
|||
inbox.postValue(Resource.error(R.string.generic_null_response, getCurrentDirectInbox())); |
|||
hasOlder = false; |
|||
return; |
|||
private val currentDirectInbox: DirectInbox? |
|||
get() { |
|||
val inboxResource = inbox.value |
|||
return inboxResource?.data |
|||
} |
|||
if (!Objects.equals(response.getStatus(), "ok")) { |
|||
Log.e(TAG, "DM inbox fetch response: status not ok"); |
|||
inbox.postValue(Resource.error(R.string.generic_not_ok_response, getCurrentDirectInbox())); |
|||
hasOlder = false; |
|||
return; |
|||
|
|||
private fun parseInboxResponse(response: DirectInboxResponse) { |
|||
if (response.status != "ok") { |
|||
Log.e(TAG, "DM inbox fetch response: status not ok") |
|||
inbox.postValue(error(R.string.generic_not_ok_response, currentDirectInbox)) |
|||
hasOlder = false |
|||
return |
|||
} |
|||
seqId = response.getSeqId(); |
|||
seqId = response.seqId |
|||
if (viewer == null) { |
|||
viewer = response.getViewer(); |
|||
viewer = response.viewer |
|||
} |
|||
final DirectInbox inbox = response.getInbox(); |
|||
if (inbox == null) return; |
|||
if (!TextUtils.isEmpty(cursor)) { |
|||
final DirectInbox currentDirectInbox = getCurrentDirectInbox(); |
|||
if (currentDirectInbox != null) { |
|||
List<DirectThread> threads = currentDirectInbox.getThreads(); |
|||
threads = threads == null ? new LinkedList<>() : new LinkedList<>(threads); |
|||
threads.addAll(inbox.getThreads() == null ? Collections.emptyList() : inbox.getThreads()); |
|||
inbox.setThreads(threads); |
|||
val inbox = response.inbox ?: return |
|||
if (!cursor.isNullOrBlank()) { |
|||
val currentDirectInbox = currentDirectInbox |
|||
currentDirectInbox?.let { |
|||
val threads = it.threads |
|||
val threadsCopy = if (threads == null) LinkedList() else LinkedList(threads) |
|||
threadsCopy.addAll(inbox.threads ?: emptyList()) |
|||
inbox.threads = threads |
|||
} |
|||
} |
|||
this.inbox.postValue(Resource.success(inbox)); |
|||
cursor = inbox.getOldestCursor(); |
|||
hasOlder = inbox.getHasOlder(); |
|||
pendingRequestsTotal.postValue(response.getPendingRequestsTotal()); |
|||
this.inbox.postValue(success(inbox)) |
|||
cursor = inbox.oldestCursor |
|||
hasOlder = inbox.hasOlder |
|||
pendingRequestsTotal.postValue(response.pendingRequestsTotal) |
|||
} |
|||
|
|||
public void setThread(@NonNull final String threadId, |
|||
@NonNull final DirectThread thread) { |
|||
final DirectInbox inbox = getCurrentDirectInbox(); |
|||
if (inbox == null) return; |
|||
final int index = getThreadIndex(threadId, inbox); |
|||
setThread(inbox, index, thread); |
|||
fun setThread( |
|||
threadId: String, |
|||
thread: DirectThread, |
|||
) { |
|||
val inbox = currentDirectInbox ?: return |
|||
val index = getThreadIndex(threadId, inbox) |
|||
setThread(inbox, index, thread) |
|||
} |
|||
|
|||
private void setThread(@NonNull final DirectInbox inbox, |
|||
final int index, |
|||
@NonNull final DirectThread thread) { |
|||
if (index < 0) return; |
|||
synchronized (this.inbox) { |
|||
final List<DirectThread> threadsCopy = new LinkedList<>(inbox.getThreads()); |
|||
threadsCopy.set(index, thread); |
|||
private fun setThread( |
|||
inbox: DirectInbox, |
|||
index: Int, |
|||
thread: DirectThread, |
|||
) { |
|||
if (index < 0) return |
|||
synchronized(this.inbox) { |
|||
val threads = inbox.threads |
|||
val threadsCopy = if (threads == null) LinkedList() else LinkedList(threads) |
|||
threadsCopy[index] = thread |
|||
try { |
|||
final DirectInbox clone = (DirectInbox) inbox.clone(); |
|||
clone.setThreads(threadsCopy); |
|||
this.inbox.postValue(Resource.success(clone)); |
|||
} catch (CloneNotSupportedException e) { |
|||
Log.e(TAG, "setThread: ", e); |
|||
val clone = inbox.clone() as DirectInbox |
|||
clone.threads = threadsCopy |
|||
this.inbox.postValue(success(clone)) |
|||
} catch (e: CloneNotSupportedException) { |
|||
Log.e(TAG, "setThread: ", e) |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void addItemsToThread(@NonNull final String threadId, |
|||
final int insertIndex, |
|||
@NonNull final Collection<DirectItem> items) { |
|||
final DirectInbox inbox = getCurrentDirectInbox(); |
|||
if (inbox == null) return; |
|||
synchronized (THREAD_LOCKS.getUnchecked(threadId)) { |
|||
final int index = getThreadIndex(threadId, inbox); |
|||
if (index < 0) return; |
|||
final List<DirectThread> threads = inbox.getThreads(); |
|||
final DirectThread thread = threads.get(index); |
|||
List<DirectItem> list = thread.getItems(); |
|||
list = list == null ? new LinkedList<>() : new LinkedList<>(list); |
|||
fun addItemsToThread( |
|||
threadId: String, |
|||
insertIndex: Int, |
|||
items: Collection<DirectItem>, |
|||
) { |
|||
val inbox = currentDirectInbox ?: return |
|||
synchronized(THREAD_LOCKS.getUnchecked(threadId)) { |
|||
val index = getThreadIndex(threadId, inbox) |
|||
if (index < 0) return |
|||
val threads = inbox.threads ?: return |
|||
val thread = threads[index] |
|||
val threadItems = thread.items |
|||
val list = if (threadItems == null) LinkedList() else LinkedList(threadItems) |
|||
if (insertIndex >= 0) { |
|||
list.addAll(insertIndex, items); |
|||
list.addAll(insertIndex, items) |
|||
} else { |
|||
list.addAll(items); |
|||
list.addAll(items) |
|||
} |
|||
try { |
|||
final DirectThread threadClone = (DirectThread) thread.clone(); |
|||
threadClone.setItems(list); |
|||
setThread(inbox, index, threadClone); |
|||
} catch (Exception e) { |
|||
Log.e(TAG, "addItemsToThread: ", e); |
|||
val threadClone = thread.clone() as DirectThread |
|||
threadClone.items = list |
|||
setThread(inbox, index, threadClone) |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "addItemsToThread: ", e) |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void setItemsToThread(@NonNull final String threadId, |
|||
@NonNull final List<DirectItem> updatedItems) { |
|||
final DirectInbox inbox = getCurrentDirectInbox(); |
|||
if (inbox == null) return; |
|||
synchronized (THREAD_LOCKS.getUnchecked(threadId)) { |
|||
final int index = getThreadIndex(threadId, inbox); |
|||
if (index < 0) return; |
|||
final List<DirectThread> threads = inbox.getThreads(); |
|||
final DirectThread thread = threads.get(index); |
|||
fun setItemsToThread( |
|||
threadId: String, |
|||
updatedItems: List<DirectItem>, |
|||
) { |
|||
val inbox = currentDirectInbox ?: return |
|||
synchronized(THREAD_LOCKS.getUnchecked(threadId)) { |
|||
val index = getThreadIndex(threadId, inbox) |
|||
if (index < 0) return |
|||
val threads = inbox.threads ?: return |
|||
val thread = threads[index] |
|||
try { |
|||
final DirectThread threadClone = (DirectThread) thread.clone(); |
|||
threadClone.setItems(updatedItems); |
|||
setThread(inbox, index, threadClone); |
|||
} catch (Exception e) { |
|||
Log.e(TAG, "setItemsToThread: ", e); |
|||
val threadClone = thread.clone() as DirectThread |
|||
threadClone.items = updatedItems |
|||
setThread(inbox, index, threadClone) |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "setItemsToThread: ", e) |
|||
} |
|||
} |
|||
} |
|||
|
|||
private int getThreadIndex(@NonNull final String threadId, |
|||
@NonNull final DirectInbox inbox) { |
|||
final List<DirectThread> threads = inbox.getThreads(); |
|||
if (threads == null || threads.isEmpty()) { |
|||
return -1; |
|||
} |
|||
return Iterables.indexOf(threads, t -> { |
|||
if (t == null) return false; |
|||
return t.getThreadId().equals(threadId); |
|||
}); |
|||
private fun getThreadIndex( |
|||
threadId: String, |
|||
inbox: DirectInbox, |
|||
): Int { |
|||
val threads = inbox.threads |
|||
return if (threads == null || threads.isEmpty()) { |
|||
-1 |
|||
} else threads.indexOfFirst { it.threadId == threadId } |
|||
} |
|||
|
|||
private Integer getCurrentUnseenCount() { |
|||
final Resource<Integer> unseenCountResource = unseenCount.getValue(); |
|||
return unseenCountResource != null ? unseenCountResource.data : null; |
|||
} |
|||
private val currentUnseenCount: Int? |
|||
get() { |
|||
val unseenCountResource = unseenCount.value |
|||
return unseenCountResource?.data |
|||
} |
|||
|
|||
private void stopCurrentInboxRequest() { |
|||
if (inboxRequest == null || inboxRequest.isCanceled() || inboxRequest.isExecuted()) return; |
|||
inboxRequest.cancel(); |
|||
inboxRequest = null; |
|||
private fun stopCurrentInboxRequest() { |
|||
inboxRequest?.let { |
|||
if (it.isCanceled || it.isExecuted) return |
|||
it.cancel() |
|||
} |
|||
inboxRequest = null |
|||
} |
|||
|
|||
private void stopCurrentUnseenCountRequest() { |
|||
if (unseenCountRequest == null || unseenCountRequest.isCanceled() || unseenCountRequest.isExecuted()) return; |
|||
unseenCountRequest.cancel(); |
|||
unseenCountRequest = null; |
|||
private fun stopCurrentUnseenCountRequest() { |
|||
unseenCountRequest?.let { |
|||
if (it.isCanceled || it.isExecuted) return |
|||
it.cancel() |
|||
} |
|||
unseenCountRequest = null |
|||
} |
|||
|
|||
public void onDestroy() { |
|||
stopCurrentInboxRequest(); |
|||
stopCurrentUnseenCountRequest(); |
|||
fun onDestroy() { |
|||
stopCurrentInboxRequest() |
|||
stopCurrentUnseenCountRequest() |
|||
} |
|||
|
|||
public void addThread(@NonNull final DirectThread thread, final int insertIndex) { |
|||
if (insertIndex < 0) return; |
|||
synchronized (this.inbox) { |
|||
final DirectInbox currentDirectInbox = getCurrentDirectInbox(); |
|||
if (currentDirectInbox == null) return; |
|||
final List<DirectThread> threadsCopy = new LinkedList<>(currentDirectInbox.getThreads()); |
|||
threadsCopy.add(insertIndex, thread); |
|||
fun addThread(thread: DirectThread, insertIndex: Int) { |
|||
if (insertIndex < 0) return |
|||
synchronized(inbox) { |
|||
val currentDirectInbox = currentDirectInbox ?: return |
|||
val threads = currentDirectInbox.threads |
|||
val threadsCopy = if (threads == null) LinkedList() else LinkedList(threads) |
|||
threadsCopy.add(insertIndex, thread) |
|||
try { |
|||
final DirectInbox clone = (DirectInbox) currentDirectInbox.clone(); |
|||
clone.setThreads(threadsCopy); |
|||
this.inbox.setValue(Resource.success(clone)); |
|||
} catch (CloneNotSupportedException e) { |
|||
Log.e(TAG, "setThread: ", e); |
|||
val clone = currentDirectInbox.clone() as DirectInbox |
|||
clone.threads = threadsCopy |
|||
inbox.setValue(success(clone)) |
|||
} catch (e: CloneNotSupportedException) { |
|||
Log.e(TAG, "setThread: ", e) |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void removeThread(@NonNull final String threadId) { |
|||
synchronized (this.inbox) { |
|||
final DirectInbox currentDirectInbox = getCurrentDirectInbox(); |
|||
if (currentDirectInbox == null) return; |
|||
final List<DirectThread> threadsCopy = currentDirectInbox.getThreads() |
|||
.stream() |
|||
.filter(t -> !t.getThreadId().equals(threadId)) |
|||
.collect(Collectors.toList()); |
|||
fun removeThread(threadId: String) { |
|||
synchronized(inbox) { |
|||
val currentDirectInbox = currentDirectInbox ?: return |
|||
val threads = currentDirectInbox.threads ?: return |
|||
val threadsCopy = threads.asSequence().filter { it.threadId != threadId }.toList() |
|||
try { |
|||
final DirectInbox clone = (DirectInbox) currentDirectInbox.clone(); |
|||
clone.setThreads(threadsCopy); |
|||
this.inbox.postValue(Resource.success(clone)); |
|||
} catch (CloneNotSupportedException e) { |
|||
Log.e(TAG, "setThread: ", e); |
|||
val clone = currentDirectInbox.clone() as DirectInbox |
|||
clone.threads = threadsCopy |
|||
inbox.postValue(success(clone)) |
|||
} catch (e: CloneNotSupportedException) { |
|||
Log.e(TAG, "setThread: ", e) |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void setPendingRequestsTotal(final int total) { |
|||
pendingRequestsTotal.postValue(total); |
|||
fun setPendingRequestsTotal(total: Int) { |
|||
pendingRequestsTotal.postValue(total) |
|||
} |
|||
|
|||
public boolean containsThread(final String threadId) { |
|||
if (threadId == null) return false; |
|||
synchronized (this.inbox) { |
|||
final DirectInbox currentDirectInbox = getCurrentDirectInbox(); |
|||
if (currentDirectInbox == null) return false; |
|||
final List<DirectThread> threads = currentDirectInbox.getThreads(); |
|||
if (threads == null) return false; |
|||
return threads.stream().anyMatch(thread -> Objects.equals(thread.getThreadId(), threadId)); |
|||
fun containsThread(threadId: String?): Boolean { |
|||
if (threadId == null) return false |
|||
synchronized(inbox) { |
|||
val currentDirectInbox = currentDirectInbox ?: return false |
|||
val threads = currentDirectInbox.threads ?: return false |
|||
return threads.any { it.threadId == threadId } |
|||
} |
|||
} |
|||
|
|||
companion object { |
|||
private val TAG = InboxManager::class.java.simpleName |
|||
private val THREAD_LOCKS = CacheBuilder |
|||
.newBuilder() |
|||
.expireAfterAccess(1, TimeUnit.MINUTES) // max lock time ever expected |
|||
.build<String, Any>(CacheLoader.from<Any> { Object() }) |
|||
private val THREAD_COMPARATOR = Comparator { t1: DirectThread, t2: DirectThread -> |
|||
val t1FirstDirectItem = t1.firstDirectItem |
|||
val t2FirstDirectItem = t2.firstDirectItem |
|||
if (t1FirstDirectItem == null && t2FirstDirectItem == null) return@Comparator 0 |
|||
if (t1FirstDirectItem == null) return@Comparator 1 |
|||
if (t2FirstDirectItem == null) return@Comparator -1 |
|||
t2FirstDirectItem.getTimestamp().compareTo(t1FirstDirectItem.getTimestamp()) |
|||
} |
|||
|
|||
fun getInstance(pending: Boolean): InboxManager { |
|||
return InboxManager(pending) |
|||
} |
|||
} |
|||
|
|||
init { |
|||
val cookie = Utils.settingsHelper.getString(Constants.COOKIE) |
|||
val viewerId = getUserIdFromCookie(cookie) |
|||
val deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID) |
|||
val csrfToken = getCsrfTokenFromCookie(cookie) |
|||
require(!csrfToken.isNullOrBlank() && viewerId != 0L && deviceUuid.isNotBlank()) { "User is not logged in!" } |
|||
service = getInstance(csrfToken, viewerId, deviceUuid) |
|||
|
|||
// Transformations |
|||
threads = Transformations.distinctUntilChanged(Transformations.map(inbox) { inboxResource: Resource<DirectInbox?>? -> |
|||
if (inboxResource == null) { |
|||
return@map emptyList() |
|||
} |
|||
val inbox = inboxResource.data |
|||
val threads = inbox?.threads ?: emptyList() |
|||
ImmutableList.sortedCopyOf(THREAD_COMPARATOR, threads) |
|||
}) |
|||
fetchInbox() |
|||
if (!pending) { |
|||
fetchUnseenCount() |
|||
} |
|||
} |
|||
} |
1880
app/src/main/java/awais/instagrabber/managers/ThreadManager.java
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
1675
app/src/main/java/awais/instagrabber/managers/ThreadManager.kt
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,193 +0,0 @@ |
|||
package awais.instagrabber.utils; |
|||
|
|||
import android.content.ContentResolver; |
|||
import android.graphics.Bitmap; |
|||
import android.net.Uri; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
|
|||
import org.json.JSONObject; |
|||
|
|||
import java.io.File; |
|||
import java.io.FileInputStream; |
|||
import java.io.IOException; |
|||
import java.io.InputStream; |
|||
import java.util.Map; |
|||
|
|||
import awais.instagrabber.models.UploadPhotoOptions; |
|||
import awais.instagrabber.models.UploadVideoOptions; |
|||
import awais.instagrabber.webservices.interceptors.AddCookiesInterceptor; |
|||
import okhttp3.Call; |
|||
import okhttp3.Headers; |
|||
import okhttp3.MediaType; |
|||
import okhttp3.OkHttpClient; |
|||
import okhttp3.Request; |
|||
import okhttp3.RequestBody; |
|||
import okhttp3.Response; |
|||
import okhttp3.ResponseBody; |
|||
import okio.BufferedSink; |
|||
import okio.Okio; |
|||
import okio.Source; |
|||
|
|||
public final class MediaUploader { |
|||
private static final String TAG = MediaUploader.class.getSimpleName(); |
|||
private static final String HOST = "https://i.instagram.com"; |
|||
private static final AppExecutors appExecutors = AppExecutors.INSTANCE; |
|||
|
|||
public static void uploadPhoto(@NonNull final Uri uri, |
|||
@NonNull final ContentResolver contentResolver, |
|||
@NonNull final OnMediaUploadCompleteListener listener) { |
|||
BitmapUtils.loadBitmap(contentResolver, uri, 1000, false, new BitmapUtils.ThumbnailLoadCallback() { |
|||
@Override |
|||
public void onLoad(@Nullable final Bitmap bitmap, final int width, final int height) { |
|||
if (bitmap == null) { |
|||
listener.onFailure(new RuntimeException("Bitmap result was null")); |
|||
return; |
|||
} |
|||
uploadPhoto(bitmap, listener); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(@NonNull final Throwable t) { |
|||
listener.onFailure(t); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private static void uploadPhoto(@NonNull final Bitmap bitmap, |
|||
@NonNull final OnMediaUploadCompleteListener listener) { |
|||
appExecutors.getTasksThread().submit(() -> { |
|||
final File file; |
|||
final long byteLength; |
|||
try { |
|||
file = BitmapUtils.convertToJpegAndSaveToFile(bitmap, null); |
|||
byteLength = file.length(); |
|||
} catch (Exception e) { |
|||
listener.onFailure(e); |
|||
return; |
|||
} |
|||
final UploadPhotoOptions options = MediaUploadHelper.createUploadPhotoOptions(byteLength); |
|||
final Map<String, String> headers = MediaUploadHelper.getUploadPhotoHeaders(options); |
|||
final String url = HOST + "/rupload_igphoto/" + options.getName() + "/"; |
|||
appExecutors.getNetworkIO().execute(() -> { |
|||
try (FileInputStream input = new FileInputStream(file)) { |
|||
upload(input, url, headers, listener); |
|||
} catch (IOException e) { |
|||
listener.onFailure(e); |
|||
} finally { |
|||
//noinspection ResultOfMethodCallIgnored |
|||
file.delete(); |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
public static void uploadVideo(final Uri uri, |
|||
final ContentResolver contentResolver, |
|||
final UploadVideoOptions options, |
|||
final OnMediaUploadCompleteListener listener) { |
|||
appExecutors.getTasksThread().submit(() -> { |
|||
final Map<String, String> headers = MediaUploadHelper.getUploadVideoHeaders(options); |
|||
final String url = HOST + "/rupload_igvideo/" + options.getName() + "/"; |
|||
appExecutors.getNetworkIO().execute(() -> { |
|||
try (InputStream input = contentResolver.openInputStream(uri)) { |
|||
if (input == null) { |
|||
listener.onFailure(new RuntimeException("InputStream was null")); |
|||
return; |
|||
} |
|||
upload(input, url, headers, listener); |
|||
} catch (IOException e) { |
|||
listener.onFailure(e); |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
private static void upload(@NonNull final InputStream input, |
|||
@NonNull final String url, |
|||
@NonNull final Map<String, String> headers, |
|||
@NonNull final OnMediaUploadCompleteListener listener) { |
|||
try { |
|||
final OkHttpClient client = new OkHttpClient.Builder() |
|||
// .addInterceptor(new LoggingInterceptor()) |
|||
.addInterceptor(new AddCookiesInterceptor()) |
|||
.followRedirects(false) |
|||
.followSslRedirects(false) |
|||
.build(); |
|||
final Request request = new Request.Builder() |
|||
.headers(Headers.of(headers)) |
|||
.url(url) |
|||
.post(create(MediaType.parse("application/octet-stream"), input)) |
|||
.build(); |
|||
final Call call = client.newCall(request); |
|||
final Response response = call.execute(); |
|||
final ResponseBody body = response.body(); |
|||
if (!response.isSuccessful()) { |
|||
listener.onFailure(new IOException("Unexpected code " + response + (body != null ? ": " + body.string() : ""))); |
|||
return; |
|||
} |
|||
listener.onUploadComplete(new MediaUploadResponse(response.code(), body != null ? new JSONObject(body.string()) : null)); |
|||
} catch (Exception e) { |
|||
listener.onFailure(e); |
|||
} |
|||
} |
|||
|
|||
public interface OnMediaUploadCompleteListener { |
|||
void onUploadComplete(MediaUploadResponse response); |
|||
|
|||
void onFailure(Throwable t); |
|||
} |
|||
|
|||
private static RequestBody create(final MediaType mediaType, final InputStream inputStream) { |
|||
return new RequestBody() { |
|||
@Override |
|||
public MediaType contentType() { |
|||
return mediaType; |
|||
} |
|||
|
|||
@Override |
|||
public long contentLength() { |
|||
try { |
|||
return inputStream.available(); |
|||
} catch (IOException e) { |
|||
return 0; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void writeTo(@NonNull BufferedSink sink) throws IOException { |
|||
try (Source source = Okio.source(inputStream)) { |
|||
sink.writeAll(source); |
|||
} |
|||
} |
|||
}; |
|||
} |
|||
|
|||
public static class MediaUploadResponse { |
|||
private final int responseCode; |
|||
private final JSONObject response; |
|||
|
|||
public MediaUploadResponse(int responseCode, JSONObject response) { |
|||
this.responseCode = responseCode; |
|||
this.response = response; |
|||
} |
|||
|
|||
public int getResponseCode() { |
|||
return responseCode; |
|||
} |
|||
|
|||
public JSONObject getResponse() { |
|||
return response; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public String toString() { |
|||
return "MediaUploadResponse{" + |
|||
"responseCode=" + responseCode + |
|||
", response=" + response + |
|||
'}'; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,155 @@ |
|||
package awais.instagrabber.utils |
|||
|
|||
import android.content.ContentResolver |
|||
import android.graphics.Bitmap |
|||
import android.net.Uri |
|||
import awais.instagrabber.models.UploadVideoOptions |
|||
import awais.instagrabber.utils.BitmapUtils.ThumbnailLoadCallback |
|||
import awais.instagrabber.webservices.interceptors.AddCookiesInterceptor |
|||
import okhttp3.* |
|||
import okio.BufferedSink |
|||
import okio.source |
|||
import org.json.JSONObject |
|||
import java.io.File |
|||
import java.io.FileInputStream |
|||
import java.io.IOException |
|||
import java.io.InputStream |
|||
|
|||
object MediaUploader { |
|||
private const val HOST = "https://i.instagram.com" |
|||
private val appExecutors = AppExecutors |
|||
|
|||
fun uploadPhoto( |
|||
uri: Uri, |
|||
contentResolver: ContentResolver, |
|||
listener: OnMediaUploadCompleteListener, |
|||
) { |
|||
BitmapUtils.loadBitmap(contentResolver, uri, 1000f, false, object : ThumbnailLoadCallback { |
|||
override fun onLoad(bitmap: Bitmap?, width: Int, height: Int) { |
|||
if (bitmap == null) { |
|||
listener.onFailure(RuntimeException("Bitmap result was null")) |
|||
return |
|||
} |
|||
uploadPhoto(bitmap, listener) |
|||
} |
|||
|
|||
override fun onFailure(t: Throwable) { |
|||
listener.onFailure(t) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
private fun uploadPhoto( |
|||
bitmap: Bitmap, |
|||
listener: OnMediaUploadCompleteListener, |
|||
) { |
|||
appExecutors.tasksThread.submit { |
|||
val file: File |
|||
val byteLength: Long |
|||
try { |
|||
file = BitmapUtils.convertToJpegAndSaveToFile(bitmap, null) |
|||
byteLength = file.length() |
|||
} catch (e: Exception) { |
|||
listener.onFailure(e) |
|||
return@submit |
|||
} |
|||
val options = createUploadPhotoOptions(byteLength) |
|||
val headers = getUploadPhotoHeaders(options) |
|||
val url = HOST + "/rupload_igphoto/" + options.name + "/" |
|||
appExecutors.networkIO.execute { |
|||
try { |
|||
FileInputStream(file).use { input -> upload(input, url, headers, listener) } |
|||
} catch (e: IOException) { |
|||
listener.onFailure(e) |
|||
} finally { |
|||
file.delete() |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@JvmStatic |
|||
fun uploadVideo( |
|||
uri: Uri, |
|||
contentResolver: ContentResolver, |
|||
options: UploadVideoOptions, |
|||
listener: OnMediaUploadCompleteListener, |
|||
) { |
|||
appExecutors.tasksThread.submit { |
|||
val headers = getUploadVideoHeaders(options) |
|||
val url = HOST + "/rupload_igvideo/" + options.name + "/" |
|||
appExecutors.networkIO.execute { |
|||
try { |
|||
contentResolver.openInputStream(uri).use { input -> |
|||
if (input == null) { |
|||
listener.onFailure(RuntimeException("InputStream was null")) |
|||
return@execute |
|||
} |
|||
upload(input, url, headers, listener) |
|||
} |
|||
} catch (e: IOException) { |
|||
listener.onFailure(e) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private fun upload( |
|||
input: InputStream, |
|||
url: String, |
|||
headers: Map<String, String>, |
|||
listener: OnMediaUploadCompleteListener, |
|||
) { |
|||
try { |
|||
val client = OkHttpClient.Builder() |
|||
// .addInterceptor(new LoggingInterceptor()) |
|||
.addInterceptor(AddCookiesInterceptor()) |
|||
.followRedirects(false) |
|||
.followSslRedirects(false) |
|||
.build() |
|||
val request = Request.Builder() |
|||
.headers(Headers.of(headers)) |
|||
.url(url) |
|||
.post(create(MediaType.parse("application/octet-stream"), input)) |
|||
.build() |
|||
val call = client.newCall(request) |
|||
val response = call.execute() |
|||
val body = response.body() |
|||
if (!response.isSuccessful) { |
|||
listener.onFailure(IOException("Unexpected code " + response + if (body != null) ": " + body.string() else "")) |
|||
return |
|||
} |
|||
listener.onUploadComplete(MediaUploadResponse(response.code(), if (body != null) JSONObject(body.string()) else null)) |
|||
} catch (e: Exception) { |
|||
listener.onFailure(e) |
|||
} |
|||
} |
|||
|
|||
private fun create(mediaType: MediaType?, inputStream: InputStream): RequestBody { |
|||
return object : RequestBody() { |
|||
override fun contentType(): MediaType? { |
|||
return mediaType |
|||
} |
|||
|
|||
override fun contentLength(): Long { |
|||
return try { |
|||
inputStream.available().toLong() |
|||
} catch (e: IOException) { |
|||
0 |
|||
} |
|||
} |
|||
|
|||
@Throws(IOException::class) |
|||
override fun writeTo(sink: BufferedSink) { |
|||
inputStream.source().use { sink.writeAll(it) } |
|||
} |
|||
} |
|||
} |
|||
|
|||
interface OnMediaUploadCompleteListener { |
|||
fun onUploadComplete(response: MediaUploadResponse) |
|||
fun onFailure(t: Throwable) |
|||
} |
|||
|
|||
data class MediaUploadResponse(val responseCode: Int, val response: JSONObject?) |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue