2020-09-10 11:44:25 +00:00
|
|
|
package awais.instagrabber.utils;
|
|
|
|
|
|
|
|
import android.Manifest;
|
|
|
|
import android.app.Activity;
|
|
|
|
import android.content.Context;
|
2020-11-03 10:01:59 +00:00
|
|
|
import android.content.DialogInterface;
|
2020-09-10 11:44:25 +00:00
|
|
|
import android.content.pm.PackageManager;
|
|
|
|
import android.os.Environment;
|
2020-11-07 06:57:10 +00:00
|
|
|
import android.util.Log;
|
2020-10-17 10:07:03 +00:00
|
|
|
import android.webkit.MimeTypeMap;
|
2020-09-10 11:44:25 +00:00
|
|
|
import android.widget.Toast;
|
|
|
|
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
import androidx.annotation.Nullable;
|
2020-11-03 10:01:59 +00:00
|
|
|
import androidx.appcompat.app.AlertDialog;
|
2020-09-10 11:44:25 +00:00
|
|
|
import androidx.core.app.ActivityCompat;
|
|
|
|
import androidx.core.content.ContextCompat;
|
2020-10-17 10:07:03 +00:00
|
|
|
import androidx.work.Constraints;
|
|
|
|
import androidx.work.Data;
|
|
|
|
import androidx.work.NetworkType;
|
|
|
|
import androidx.work.OneTimeWorkRequest;
|
|
|
|
import androidx.work.WorkManager;
|
|
|
|
import androidx.work.WorkRequest;
|
|
|
|
|
|
|
|
import com.google.gson.Gson;
|
2020-09-10 11:44:25 +00:00
|
|
|
|
2020-11-07 06:57:10 +00:00
|
|
|
import java.io.BufferedWriter;
|
2020-09-10 11:44:25 +00:00
|
|
|
import java.io.File;
|
2020-11-07 06:57:10 +00:00
|
|
|
import java.io.FileWriter;
|
|
|
|
import java.io.IOException;
|
2020-10-17 10:07:03 +00:00
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.HashMap;
|
2020-11-06 12:46:31 +00:00
|
|
|
import java.util.LinkedList;
|
2020-09-10 11:44:25 +00:00
|
|
|
import java.util.List;
|
2020-10-17 10:07:03 +00:00
|
|
|
import java.util.Map;
|
|
|
|
import java.util.UUID;
|
2020-09-10 11:44:25 +00:00
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
|
|
|
import awais.instagrabber.R;
|
2020-10-17 10:07:03 +00:00
|
|
|
import awais.instagrabber.models.FeedModel;
|
|
|
|
import awais.instagrabber.models.PostChild;
|
2020-11-06 12:46:31 +00:00
|
|
|
import awais.instagrabber.models.StoryModel;
|
|
|
|
import awais.instagrabber.models.enums.MediaItemType;
|
2020-10-17 10:07:03 +00:00
|
|
|
import awais.instagrabber.workers.DownloadWorker;
|
2020-09-10 11:44:25 +00:00
|
|
|
|
|
|
|
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
|
|
|
|
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
|
|
|
|
|
|
|
|
public final class DownloadUtils {
|
2020-11-07 06:57:10 +00:00
|
|
|
private static final String TAG = "DownloadUtils";
|
|
|
|
|
2020-10-17 10:07:03 +00:00
|
|
|
public static final String WRITE_PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE;
|
2020-09-10 11:44:25 +00:00
|
|
|
public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
|
|
|
|
2020-11-05 18:34:01 +00:00
|
|
|
@NonNull
|
|
|
|
private static File getDownloadDir() {
|
|
|
|
File dir = new File(Environment.getExternalStorageDirectory(), "Download");
|
2020-09-10 11:44:25 +00:00
|
|
|
|
2020-11-05 18:34:01 +00:00
|
|
|
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
|
|
|
|
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH);
|
|
|
|
if (!TextUtils.isEmpty(customPath)) {
|
|
|
|
dir = new File(customPath);
|
2020-09-10 11:44:25 +00:00
|
|
|
}
|
|
|
|
}
|
2020-11-05 18:34:01 +00:00
|
|
|
return dir;
|
2020-09-10 11:44:25 +00:00
|
|
|
}
|
|
|
|
|
2020-10-17 10:07:03 +00:00
|
|
|
@Nullable
|
|
|
|
private static File getDownloadDir(@NonNull final Context context, @Nullable final String username) {
|
2020-11-06 12:46:31 +00:00
|
|
|
return getDownloadDir(context, username, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nullable
|
|
|
|
private static File getDownloadDir(final Context context,
|
|
|
|
@Nullable final String username,
|
|
|
|
final boolean skipCreateDir) {
|
2020-11-05 18:34:01 +00:00
|
|
|
File dir = getDownloadDir();
|
2020-10-17 10:07:03 +00:00
|
|
|
|
|
|
|
if (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !TextUtils.isEmpty(username)) {
|
2020-11-30 17:46:44 +00:00
|
|
|
final String finaleUsername = username.startsWith("@") ? username.substring(1) : username;
|
2020-10-17 10:07:03 +00:00
|
|
|
dir = new File(dir, finaleUsername);
|
|
|
|
}
|
|
|
|
|
2020-11-06 12:46:31 +00:00
|
|
|
if (context != null && !skipCreateDir && !dir.exists() && !dir.mkdirs()) {
|
2020-10-17 10:07:03 +00:00
|
|
|
Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show();
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return dir;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void dmDownload(@NonNull final Context context,
|
|
|
|
@Nullable final String username,
|
2020-11-04 14:58:06 +00:00
|
|
|
final String modelId,
|
|
|
|
final String url) {
|
|
|
|
if (url == null) return;
|
|
|
|
if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
|
|
|
|
dmDownloadImpl(context, username, modelId, url);
|
|
|
|
} else if (context instanceof Activity) {
|
2020-09-10 11:44:25 +00:00
|
|
|
ActivityCompat.requestPermissions((Activity) context, PERMS, 8020);
|
2020-11-04 14:58:06 +00:00
|
|
|
}
|
2020-09-10 11:44:25 +00:00
|
|
|
}
|
|
|
|
|
2020-10-17 10:07:03 +00:00
|
|
|
private static void dmDownloadImpl(@NonNull final Context context,
|
|
|
|
@Nullable final String username,
|
2020-11-04 14:58:06 +00:00
|
|
|
final String modelId,
|
|
|
|
final String url) {
|
|
|
|
final File dir = getDownloadDir(context, username);
|
2020-09-10 11:44:25 +00:00
|
|
|
if (dir.exists() || dir.mkdirs()) {
|
2020-11-04 14:58:06 +00:00
|
|
|
download(context,
|
|
|
|
url,
|
|
|
|
getDownloadSaveFile(dir, modelId, url).getAbsolutePath());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show();
|
2020-09-10 11:44:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@NonNull
|
2020-10-17 10:07:03 +00:00
|
|
|
private static File getDownloadSaveFile(final File finalDir,
|
|
|
|
final String postId,
|
|
|
|
final String displayUrl) {
|
|
|
|
return getDownloadSaveFile(finalDir, postId, "", displayUrl);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static File getDownloadChildSaveFile(final File downloadDir,
|
|
|
|
final String postId,
|
|
|
|
final int childPosition,
|
|
|
|
final String url) {
|
|
|
|
final String sliderPostfix = "_slide_" + childPosition;
|
|
|
|
return getDownloadSaveFile(downloadDir, postId, sliderPostfix, url);
|
2020-09-10 11:44:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@NonNull
|
2020-10-17 10:07:03 +00:00
|
|
|
private static File getDownloadSaveFile(final File finalDir,
|
|
|
|
final String postId,
|
|
|
|
final String sliderPostfix,
|
|
|
|
final String displayUrl) {
|
|
|
|
final String fileName = postId + sliderPostfix + "." + getFileExtensionFromUrl(displayUrl);
|
|
|
|
return new File(finalDir, fileName);
|
2020-09-10 11:44:25 +00:00
|
|
|
}
|
|
|
|
|
2020-11-05 18:34:01 +00:00
|
|
|
@NonNull
|
|
|
|
public static File getTempFile() {
|
2021-01-02 02:54:32 +00:00
|
|
|
return getTempFile(null, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static File getTempFile(final String fileName, final String extension) {
|
2020-11-05 18:34:01 +00:00
|
|
|
final File dir = getDownloadDir();
|
2021-01-02 02:54:32 +00:00
|
|
|
String name = fileName;
|
|
|
|
if (TextUtils.isEmpty(name)) {
|
|
|
|
name = UUID.randomUUID().toString();
|
|
|
|
}
|
|
|
|
if (!TextUtils.isEmpty(extension)) {
|
|
|
|
name += "." + extension;
|
|
|
|
}
|
|
|
|
return new File(dir, name);
|
2020-11-05 18:34:01 +00:00
|
|
|
}
|
|
|
|
|
2020-10-17 10:07:03 +00:00
|
|
|
/**
|
|
|
|
* Copied from {@link MimeTypeMap#getFileExtensionFromUrl(String)})
|
|
|
|
* <p>
|
|
|
|
* Returns the file extension or an empty string if there is no
|
|
|
|
* extension. This method is a convenience method for obtaining the
|
|
|
|
* extension of a url and has undefined results for other Strings.
|
|
|
|
*
|
|
|
|
* @param url URL
|
|
|
|
* @return The file extension of the given url.
|
|
|
|
*/
|
|
|
|
public static String getFileExtensionFromUrl(String url) {
|
|
|
|
if (!TextUtils.isEmpty(url)) {
|
|
|
|
int fragment = url.lastIndexOf('#');
|
|
|
|
if (fragment > 0) {
|
|
|
|
url = url.substring(0, fragment);
|
|
|
|
}
|
|
|
|
|
|
|
|
int query = url.lastIndexOf('?');
|
|
|
|
if (query > 0) {
|
|
|
|
url = url.substring(0, query);
|
|
|
|
}
|
|
|
|
|
|
|
|
int filenamePos = url.lastIndexOf('/');
|
|
|
|
String filename =
|
|
|
|
0 <= filenamePos ? url.substring(filenamePos + 1) : url;
|
|
|
|
|
|
|
|
// if the filename contains special characters, we don't
|
|
|
|
// consider it valid for our matching purposes:
|
|
|
|
if (!filename.isEmpty() &&
|
|
|
|
Pattern.matches("[a-zA-Z_0-9.\\-()%]+", filename)) {
|
|
|
|
int dotPos = filename.lastIndexOf('.');
|
|
|
|
if (0 <= dotPos) {
|
2020-11-13 23:44:33 +00:00
|
|
|
return filename.substring(dotPos);
|
2020-10-17 10:07:03 +00:00
|
|
|
}
|
|
|
|
}
|
2020-09-10 11:44:25 +00:00
|
|
|
}
|
|
|
|
|
2020-10-17 10:07:03 +00:00
|
|
|
return "";
|
2020-09-10 11:44:25 +00:00
|
|
|
}
|
|
|
|
|
2020-11-06 12:46:31 +00:00
|
|
|
public static List<Boolean> checkDownloaded(@NonNull final FeedModel feedModel) {
|
|
|
|
final List<Boolean> checkList = new LinkedList<>();
|
|
|
|
final File downloadDir = getDownloadDir(null, "@" + feedModel.getProfileModel().getUsername(), true);
|
|
|
|
switch (feedModel.getItemType()) {
|
|
|
|
case MEDIA_TYPE_IMAGE:
|
|
|
|
case MEDIA_TYPE_VIDEO: {
|
|
|
|
final String url = feedModel.getDisplayUrl();
|
2020-11-06 23:09:29 +00:00
|
|
|
final File file = getDownloadSaveFile(downloadDir, feedModel.getShortCode(), url);
|
2020-11-06 12:46:31 +00:00
|
|
|
checkList.add(file.exists());
|
|
|
|
break;
|
2020-09-10 11:44:25 +00:00
|
|
|
}
|
2020-11-06 12:46:31 +00:00
|
|
|
case MEDIA_TYPE_SLIDER:
|
|
|
|
final List<PostChild> sliderItems = feedModel.getSliderItems();
|
|
|
|
for (int i = 0; i < sliderItems.size(); i++) {
|
|
|
|
final PostChild child = sliderItems.get(i);
|
|
|
|
final String url = child.getDisplayUrl();
|
2020-11-06 23:09:29 +00:00
|
|
|
final File file = getDownloadChildSaveFile(downloadDir, feedModel.getShortCode(), i + 1, url);
|
2020-11-06 12:46:31 +00:00
|
|
|
checkList.add(file.exists());
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2020-09-10 11:44:25 +00:00
|
|
|
}
|
2020-11-06 12:46:31 +00:00
|
|
|
return checkList;
|
2020-09-10 11:44:25 +00:00
|
|
|
}
|
2020-10-17 10:07:03 +00:00
|
|
|
|
2020-11-03 10:01:59 +00:00
|
|
|
public static void showDownloadDialog(@NonNull Context context,
|
|
|
|
@NonNull final FeedModel feedModel,
|
|
|
|
final int childPosition) {
|
|
|
|
if (childPosition >= 0) {
|
|
|
|
final DialogInterface.OnClickListener clickListener = (dialog, which) -> {
|
|
|
|
switch (which) {
|
|
|
|
case 0:
|
|
|
|
DownloadUtils.download(context, feedModel, childPosition);
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
DownloadUtils.download(context, feedModel);
|
|
|
|
break;
|
|
|
|
case DialogInterface.BUTTON_NEGATIVE:
|
|
|
|
default:
|
|
|
|
dialog.dismiss();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
final String[] items = new String[]{
|
|
|
|
context.getString(R.string.post_viewer_download_current),
|
|
|
|
context.getString(R.string.post_viewer_download_album),
|
|
|
|
};
|
|
|
|
new AlertDialog.Builder(context)
|
|
|
|
.setTitle(R.string.post_viewer_download_dialog_title)
|
|
|
|
.setItems(items, clickListener)
|
|
|
|
.setNegativeButton(R.string.cancel, null)
|
|
|
|
.show();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
DownloadUtils.download(context, feedModel);
|
|
|
|
}
|
|
|
|
|
2020-11-06 12:46:31 +00:00
|
|
|
public static void download(@NonNull final Context context,
|
|
|
|
@NonNull final StoryModel storyModel) {
|
|
|
|
final File downloadDir = getDownloadDir(context, "@" + storyModel.getUsername());
|
|
|
|
final String url = storyModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO
|
|
|
|
? storyModel.getVideoUrl()
|
|
|
|
: storyModel.getStoryUrl();
|
|
|
|
final File saveFile = new File(downloadDir,
|
|
|
|
storyModel.getStoryMediaId()
|
|
|
|
+ "_" + storyModel.getTimestamp()
|
|
|
|
+ DownloadUtils.getFileExtensionFromUrl(url));
|
|
|
|
download(context, url, saveFile.getAbsolutePath());
|
|
|
|
}
|
|
|
|
|
2020-10-17 10:07:03 +00:00
|
|
|
public static void download(@NonNull final Context context,
|
|
|
|
@NonNull final FeedModel feedModel) {
|
|
|
|
download(context, feedModel, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void download(@NonNull final Context context,
|
|
|
|
@NonNull final FeedModel feedModel,
|
|
|
|
final int position) {
|
2020-11-03 08:23:20 +00:00
|
|
|
download(context, Collections.singletonList(feedModel), position);
|
|
|
|
}
|
2020-10-17 10:07:03 +00:00
|
|
|
|
2020-11-03 08:23:20 +00:00
|
|
|
public static void download(@NonNull final Context context,
|
|
|
|
@NonNull final List<FeedModel> feedModels) {
|
|
|
|
download(context, feedModels, -1);
|
2020-10-17 10:07:03 +00:00
|
|
|
}
|
|
|
|
|
2020-11-03 08:23:20 +00:00
|
|
|
private static void download(@NonNull final Context context,
|
|
|
|
@NonNull final List<FeedModel> feedModels,
|
|
|
|
final int childPositionIfSingle) {
|
|
|
|
final Map<String, String> map = new HashMap<>();
|
|
|
|
for (final FeedModel feedModel : feedModels) {
|
|
|
|
final File downloadDir = getDownloadDir(context, "@" + feedModel.getProfileModel().getUsername());
|
|
|
|
if (downloadDir == null) return;
|
|
|
|
switch (feedModel.getItemType()) {
|
|
|
|
case MEDIA_TYPE_IMAGE:
|
|
|
|
case MEDIA_TYPE_VIDEO: {
|
|
|
|
final String url = feedModel.getDisplayUrl();
|
2020-11-06 23:09:29 +00:00
|
|
|
final File file = getDownloadSaveFile(downloadDir, feedModel.getShortCode(), url);
|
2020-11-03 08:23:20 +00:00
|
|
|
map.put(url, file.getAbsolutePath());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MEDIA_TYPE_SLIDER:
|
|
|
|
final List<PostChild> sliderItems = feedModel.getSliderItems();
|
|
|
|
for (int i = 0; i < sliderItems.size(); i++) {
|
2020-11-03 10:01:59 +00:00
|
|
|
if (childPositionIfSingle >= 0 && feedModels.size() == 1 && i != childPositionIfSingle) {
|
|
|
|
continue;
|
|
|
|
}
|
2020-11-03 08:23:20 +00:00
|
|
|
final PostChild child = sliderItems.get(i);
|
|
|
|
final String url = child.getDisplayUrl();
|
2020-11-06 23:09:29 +00:00
|
|
|
final File file = getDownloadChildSaveFile(downloadDir, feedModel.getShortCode(), i + 1, url);
|
2020-11-03 08:23:20 +00:00
|
|
|
map.put(url, file.getAbsolutePath());
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
download(context, map);
|
2020-10-17 10:07:03 +00:00
|
|
|
}
|
|
|
|
|
2020-11-03 10:01:59 +00:00
|
|
|
public static void download(final Context context,
|
|
|
|
final String url,
|
|
|
|
final String filePath) {
|
|
|
|
if (context == null || url == null || filePath == null) return;
|
|
|
|
download(context, Collections.singletonMap(url, filePath));
|
|
|
|
}
|
|
|
|
|
2020-10-17 10:07:03 +00:00
|
|
|
private static void download(final Context context, final Map<String, String> urlFilePathMap) {
|
|
|
|
final Constraints constraints = new Constraints.Builder()
|
|
|
|
.setRequiredNetworkType(NetworkType.CONNECTED)
|
|
|
|
.build();
|
|
|
|
final DownloadWorker.DownloadRequest request = DownloadWorker.DownloadRequest.builder()
|
|
|
|
.setUrlToFilePathMap(urlFilePathMap)
|
|
|
|
.build();
|
2020-11-07 06:57:10 +00:00
|
|
|
final String requestJson = new Gson().toJson(request);
|
|
|
|
final File tempFile = getTempFile();
|
|
|
|
try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))) {
|
|
|
|
writer.write(requestJson);
|
|
|
|
} catch (IOException e) {
|
|
|
|
Log.e(TAG, "download: Error writing request to file", e);
|
|
|
|
//noinspection ResultOfMethodCallIgnored
|
|
|
|
tempFile.delete();
|
|
|
|
return;
|
|
|
|
}
|
2020-10-17 10:07:03 +00:00
|
|
|
final WorkRequest downloadWorkRequest = new OneTimeWorkRequest.Builder(DownloadWorker.class)
|
|
|
|
.setInputData(
|
|
|
|
new Data.Builder()
|
2020-11-07 06:57:10 +00:00
|
|
|
.putString(DownloadWorker.KEY_DOWNLOAD_REQUEST_JSON, tempFile.getAbsolutePath())
|
2020-10-17 10:07:03 +00:00
|
|
|
.build()
|
|
|
|
)
|
|
|
|
.setConstraints(constraints)
|
|
|
|
.addTag("download")
|
|
|
|
.build();
|
|
|
|
WorkManager.getInstance(context)
|
|
|
|
.enqueue(downloadWorkRequest);
|
|
|
|
}
|
2020-09-10 11:44:25 +00:00
|
|
|
}
|