Browse Source

Convert DM managers to kotlin

renovate/org.robolectric-robolectric-4.x
Ammar Githam 4 years ago
parent
commit
59750b1026
  1. 2
      app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java
  2. 411
      app/src/main/java/awais/instagrabber/managers/DirectMessagesManager.kt
  3. 583
      app/src/main/java/awais/instagrabber/managers/InboxManager.kt
  4. 1880
      app/src/main/java/awais/instagrabber/managers/ThreadManager.java
  5. 1675
      app/src/main/java/awais/instagrabber/managers/ThreadManager.kt
  6. 2
      app/src/main/java/awais/instagrabber/services/DMSyncService.java
  7. 193
      app/src/main/java/awais/instagrabber/utils/MediaUploader.java
  8. 155
      app/src/main/java/awais/instagrabber/utils/MediaUploader.kt
  9. 2
      app/src/main/java/awais/instagrabber/viewmodels/DirectInboxViewModel.java
  10. 2
      app/src/main/java/awais/instagrabber/viewmodels/DirectPendingInboxViewModel.java
  11. 2
      app/src/main/java/awais/instagrabber/viewmodels/DirectSettingsViewModel.java
  12. 13
      app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java
  13. 4
      app/src/main/java/awais/instagrabber/viewmodels/PostViewV2ViewModel.java

2
app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java

@ -1088,7 +1088,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return; return;
} }
final InboxManager inboxManager = DirectMessagesManager.getInstance().getInboxManager();
final InboxManager inboxManager = DirectMessagesManager.INSTANCE.getInboxManager();
final DirectThread thread = response.body(); final DirectThread thread = response.body();
if (!inboxManager.containsThread(thread.getThreadId())) { if (!inboxManager.containsThread(thread.getThreadId())) {
thread.setTemp(true); thread.setTemp(true);

411
app/src/main/java/awais/instagrabber/managers/DirectMessagesManager.kt

@ -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) { 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()) { 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 { 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) { 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 // 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) { 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 // 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) { 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 { 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)
} }
} }

583
app/src/main/java/awais/instagrabber/managers/InboxManager.kt

@ -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) { 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) { 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) { 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 { 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) { if (insertIndex >= 0) {
list.addAll(insertIndex, items);
list.addAll(insertIndex, items)
} else { } else {
list.addAll(items);
list.addAll(items)
} }
try { 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 { 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 { 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 { 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

1675
app/src/main/java/awais/instagrabber/managers/ThreadManager.kt
File diff suppressed because it is too large
View File

2
app/src/main/java/awais/instagrabber/services/DMSyncService.java

@ -59,7 +59,7 @@ public class DMSyncService extends LifecycleService {
super.onCreate(); super.onCreate();
startForeground(Constants.DM_CHECK_NOTIFICATION_ID, buildForegroundNotification()); startForeground(Constants.DM_CHECK_NOTIFICATION_ID, buildForegroundNotification());
Log.d(TAG, "onCreate: Service created"); Log.d(TAG, "onCreate: Service created");
final DirectMessagesManager directMessagesManager = DirectMessagesManager.getInstance();
final DirectMessagesManager directMessagesManager = DirectMessagesManager.INSTANCE;
inboxManager = directMessagesManager.getInboxManager(); inboxManager = directMessagesManager.getInboxManager();
dmLastNotifiedRepository = DMLastNotifiedRepository.getInstance(DMLastNotifiedDataSource.getInstance(getApplicationContext())); dmLastNotifiedRepository = DMLastNotifiedRepository.getInstance(DMLastNotifiedDataSource.getInstance(getApplicationContext()));
} }

193
app/src/main/java/awais/instagrabber/utils/MediaUploader.java

@ -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 +
'}';
}
}
}

155
app/src/main/java/awais/instagrabber/utils/MediaUploader.kt

@ -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?)
}

2
app/src/main/java/awais/instagrabber/viewmodels/DirectInboxViewModel.java

@ -18,7 +18,7 @@ public class DirectInboxViewModel extends ViewModel {
private final InboxManager inboxManager; private final InboxManager inboxManager;
public DirectInboxViewModel() { public DirectInboxViewModel() {
final DirectMessagesManager messagesManager = DirectMessagesManager.getInstance();
final DirectMessagesManager messagesManager = DirectMessagesManager.INSTANCE;
inboxManager = messagesManager.getInboxManager(); inboxManager = messagesManager.getInboxManager();
} }

2
app/src/main/java/awais/instagrabber/viewmodels/DirectPendingInboxViewModel.java

@ -18,7 +18,7 @@ public class DirectPendingInboxViewModel extends ViewModel {
private final InboxManager inboxManager; private final InboxManager inboxManager;
public DirectPendingInboxViewModel() { public DirectPendingInboxViewModel() {
inboxManager = DirectMessagesManager.getInstance().getPendingInboxManager();
inboxManager = DirectMessagesManager.INSTANCE.getPendingInboxManager();
inboxManager.fetchInbox(); inboxManager.fetchInbox();
} }

2
app/src/main/java/awais/instagrabber/viewmodels/DirectSettingsViewModel.java

@ -57,7 +57,7 @@ public class DirectSettingsViewModel extends AndroidViewModel {
} }
final ContentResolver contentResolver = application.getContentResolver(); final ContentResolver contentResolver = application.getContentResolver();
resources = getApplication().getResources(); resources = getApplication().getResources();
final DirectMessagesManager messagesManager = DirectMessagesManager.getInstance();
final DirectMessagesManager messagesManager = DirectMessagesManager.INSTANCE;
threadManager = messagesManager.getThreadManager(threadId, pending, currentUser, contentResolver); threadManager = messagesManager.getThreadManager(threadId, pending, currentUser, contentResolver);
} }

13
app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java

@ -52,8 +52,8 @@ public class DirectThreadViewModel extends AndroidViewModel {
private final long viewerId; private final long viewerId;
private final String threadId; private final String threadId;
private final User currentUser; private final User currentUser;
private final ThreadManager threadManager;
private ThreadManager threadManager;
private VoiceRecorder voiceRecorder; private VoiceRecorder voiceRecorder;
public DirectThreadViewModel(@NonNull final Application application, public DirectThreadViewModel(@NonNull final Application application,
@ -73,14 +73,15 @@ public class DirectThreadViewModel extends AndroidViewModel {
} }
contentResolver = application.getContentResolver(); contentResolver = application.getContentResolver();
recordingsDir = DirectoryUtils.getOutputMediaDirectory(application, "Recordings"); recordingsDir = DirectoryUtils.getOutputMediaDirectory(application, "Recordings");
final DirectMessagesManager messagesManager = DirectMessagesManager.getInstance();
final DirectMessagesManager messagesManager = DirectMessagesManager.INSTANCE;
threadManager = messagesManager.getThreadManager(threadId, pending, currentUser, contentResolver); threadManager = messagesManager.getThreadManager(threadId, pending, currentUser, contentResolver);
threadManager.fetchPendingRequests(); threadManager.fetchPendingRequests();
} }
public void moveFromPending() { public void moveFromPending() {
DirectMessagesManager.getInstance().moveThreadFromPending(threadId);
threadManager.moveFromPending();
final DirectMessagesManager messagesManager = DirectMessagesManager.INSTANCE;
messagesManager.moveThreadFromPending(threadId);
threadManager = messagesManager.getThreadManager(threadId, false, currentUser, contentResolver);
} }
public void removeThread() { public void removeThread() {
@ -268,7 +269,7 @@ public class DirectThreadViewModel extends AndroidViewModel {
threadManager.forward(recipient, itemToForward); threadManager.forward(recipient, itemToForward);
} }
public void setReplyToItem(final DirectItem item) {
public void setReplyToItem(@Nullable final DirectItem item) {
// Log.d(TAG, "setReplyToItem: " + item); // Log.d(TAG, "setReplyToItem: " + item);
threadManager.setReplyToItem(item); threadManager.setReplyToItem(item);
} }
@ -327,7 +328,7 @@ public class DirectThreadViewModel extends AndroidViewModel {
final DirectThread thread = getThread().getValue(); final DirectThread thread = getThread().getValue();
if (thread == null) return; if (thread == null) return;
if (thread.isTemp() && (thread.getItems() == null || thread.getItems().isEmpty())) { if (thread.isTemp() && (thread.getItems() == null || thread.getItems().isEmpty())) {
final InboxManager inboxManager = DirectMessagesManager.getInstance().getInboxManager();
final InboxManager inboxManager = DirectMessagesManager.INSTANCE.getInboxManager();
inboxManager.removeThread(threadId); inboxManager.removeThread(threadId);
} }
} }

4
app/src/main/java/awais/instagrabber/viewmodels/PostViewV2ViewModel.java

@ -333,14 +333,14 @@ public class PostViewV2ViewModel extends ViewModel {
public void shareDm(@NonNull final RankedRecipient result) { public void shareDm(@NonNull final RankedRecipient result) {
if (messageManager == null) { if (messageManager == null) {
messageManager = DirectMessagesManager.getInstance();
messageManager = DirectMessagesManager.INSTANCE;
} }
messageManager.sendMedia(result, media.getId()); messageManager.sendMedia(result, media.getId());
} }
public void shareDm(@NonNull final Set<RankedRecipient> recipients) { public void shareDm(@NonNull final Set<RankedRecipient> recipients) {
if (messageManager == null) { if (messageManager == null) {
messageManager = DirectMessagesManager.getInstance();
messageManager = DirectMessagesManager.INSTANCE;
} }
messageManager.sendMedia(recipients, media.getId()); messageManager.sendMedia(recipients, media.getId());
} }

Loading…
Cancel
Save