package awais.instagrabber.utils; import android.Manifest; import android.app.Activity; import android.content.Context; import android.content.DialogInterface; import android.content.pm.PackageManager; import android.os.Environment; import android.util.Log; import android.webkit.MimeTypeMap; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; 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; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.regex.Pattern; import awais.instagrabber.R; import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.enums.MediaItemType; import awais.instagrabber.repositories.responses.Audio; import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.VideoVersion; import awais.instagrabber.workers.DownloadWorker; import static awais.instagrabber.utils.Constants.FOLDER_PATH; import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; public final class DownloadUtils { private static final String TAG = "DownloadUtils"; public static final String WRITE_PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE; public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; @NonNull private static File getDownloadDir() { File dir = new File(Environment.getExternalStorageDirectory(), "Download"); if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) { final String customPath = Utils.settingsHelper.getString(FOLDER_PATH); if (!TextUtils.isEmpty(customPath)) { dir = new File(customPath); } } return dir; } @Nullable private static File getDownloadDir(@NonNull final Context context, @Nullable final String username) { return getDownloadDir(context, username, false); } @Nullable private static File getDownloadDir(final Context context, @Nullable final String username, final boolean skipCreateDir) { File dir = getDownloadDir(); if (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !TextUtils.isEmpty(username)) { final String finaleUsername = username.startsWith("@") ? username.substring(1) : username; dir = new File(dir, finaleUsername); } if (context != null && !skipCreateDir && !dir.exists() && !dir.mkdirs()) { 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, // 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) { // ActivityCompat.requestPermissions((Activity) context, PERMS, 8020); // } // } private static void dmDownloadImpl(@NonNull final Context context, @Nullable final String username, final String modelId, final String url) { final File dir = getDownloadDir(context, username); if (dir.exists() || dir.mkdirs()) { download(context, url, getDownloadSaveFile(dir, modelId, url).getAbsolutePath()); return; } Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show(); } @NonNull 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); } @NonNull 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); } @NonNull public static File getTempFile() { return getTempFile(null, null); } public static File getTempFile(final String fileName, final String extension) { final File dir = getDownloadDir(); String name = fileName; if (TextUtils.isEmpty(name)) { name = UUID.randomUUID().toString(); } if (!TextUtils.isEmpty(extension)) { name += "." + extension; } return new File(dir, name); } /** * Copied from {@link MimeTypeMap#getFileExtensionFromUrl(String)}) *

* 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) { return filename.substring(dotPos); } } } return ""; } public static List checkDownloaded(@NonNull final Media media) { final List checkList = new LinkedList<>(); final User user = media.getUser(); String username = "username"; if (user != null) { username = user.getUsername(); } final File downloadDir = getDownloadDir(null, "@" + username, true); switch (media.getMediaType()) { case MEDIA_TYPE_IMAGE: case MEDIA_TYPE_VIDEO: { final String url = ResponseBodyUtils.getImageUrl(media); final File file = getDownloadSaveFile(downloadDir, media.getCode(), url); checkList.add(file.exists()); break; } case MEDIA_TYPE_SLIDER: final List sliderItems = media.getCarouselMedia(); for (int i = 0; i < sliderItems.size(); i++) { final Media child = sliderItems.get(i); if (child == null) continue; final String url = ResponseBodyUtils.getImageUrl(child); final File file = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url); checkList.add(file.exists()); } break; default: } return checkList; } public static void showDownloadDialog(@NonNull Context context, @NonNull final Media 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); } 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()); } public static void download(@NonNull final Context context, @NonNull final Media feedModel) { download(context, feedModel, -1); } public static void download(@NonNull final Context context, @NonNull final Media feedModel, final int position) { download(context, Collections.singletonList(feedModel), position); } public static void download(@NonNull final Context context, @NonNull final List feedModels) { download(context, feedModels, -1); } private static void download(@NonNull final Context context, @NonNull final List feedModels, final int childPositionIfSingle) { final Map map = new HashMap<>(); for (final Media media : feedModels) { final File downloadDir = getDownloadDir(context, "@" + media.getUser().getUsername()); if (downloadDir == null) return; switch (media.getMediaType()) { case MEDIA_TYPE_IMAGE: case MEDIA_TYPE_VIDEO: { final String url = getUrlOfType(media); final File file = getDownloadSaveFile(downloadDir, media.getCode(), url); map.put(url, file.getAbsolutePath()); break; } case MEDIA_TYPE_VOICE: { final String url = getUrlOfType(media); String fileName = media.getId(); final User user = media.getUser(); if (user != null) { fileName = user.getUsername() + "_" + fileName; } final File file = getDownloadSaveFile(downloadDir, fileName, url); map.put(url, file.getAbsolutePath()); break; } case MEDIA_TYPE_SLIDER: final List sliderItems = media.getCarouselMedia(); for (int i = 0; i < sliderItems.size(); i++) { if (childPositionIfSingle >= 0 && feedModels.size() == 1 && i != childPositionIfSingle) { continue; } final Media child = sliderItems.get(i); final String url = getUrlOfType(child); final File file = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url); map.put(url, file.getAbsolutePath()); } break; default: } } download(context, map); } @Nullable private static String getUrlOfType(@NonNull final Media media) { switch (media.getMediaType()) { case MEDIA_TYPE_IMAGE: { return ResponseBodyUtils.getImageUrl(media); } case MEDIA_TYPE_VIDEO: { final List videoVersions = media.getVideoVersions(); String url = null; if (videoVersions != null && !videoVersions.isEmpty()) { final VideoVersion videoVersion = videoVersions.get(0); if (videoVersion != null) { url = videoVersion.getUrl(); } } return url; } case MEDIA_TYPE_VOICE: { final Audio audio = media.getAudio(); String url = null; if (audio != null) { url = audio.getAudioSrc(); } return url; } } return null; } 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)); } private static void download(final Context context, final Map urlFilePathMap) { final Constraints constraints = new Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build(); final DownloadWorker.DownloadRequest request = DownloadWorker.DownloadRequest.builder() .setUrlToFilePathMap(urlFilePathMap) .build(); 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; } final WorkRequest downloadWorkRequest = new OneTimeWorkRequest.Builder(DownloadWorker.class) .setInputData( new Data.Builder() .putString(DownloadWorker.KEY_DOWNLOAD_REQUEST_JSON, tempFile.getAbsolutePath()) .build() ) .setConstraints(constraints) .addTag("download") .build(); WorkManager.getInstance(context) .enqueue(downloadWorkRequest); } }