From 5b741caa0ccd4282918bd7523ee26ab5381e5422 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Fri, 2 Oct 2020 10:27:35 +0200 Subject: [PATCH] ignore image-media folders that have no-images _or_ seem to be music albums (just a single cover art image) block detection notification and hide in lists related to #3239 Signed-off-by: Andy Scherzinger --- .../client/jobs/MediaFoldersDetectionWork.kt | 10 +- .../ui/activity/SyncedFoldersActivity.java | 29 +-- .../com/owncloud/android/utils/FileUtil.java | 47 ++++ .../android/utils/SyncedFolderUtils.java | 202 ++++++++++++++++++ 4 files changed, 267 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/owncloud/android/utils/FileUtil.java create mode 100644 src/main/java/com/owncloud/android/utils/SyncedFolderUtils.java diff --git a/src/main/java/com/nextcloud/client/jobs/MediaFoldersDetectionWork.kt b/src/main/java/com/nextcloud/client/jobs/MediaFoldersDetectionWork.kt index 4b07fecf1caa..f2c6f167ab51 100644 --- a/src/main/java/com/nextcloud/client/jobs/MediaFoldersDetectionWork.kt +++ b/src/main/java/com/nextcloud/client/jobs/MediaFoldersDetectionWork.kt @@ -45,6 +45,7 @@ import com.nextcloud.client.preferences.AppPreferences import com.nextcloud.client.preferences.AppPreferencesImpl import com.owncloud.android.R import com.owncloud.android.datamodel.ArbitraryDataProvider +import com.owncloud.android.datamodel.MediaFolderType import com.owncloud.android.datamodel.MediaFoldersModel import com.owncloud.android.datamodel.MediaProvider import com.owncloud.android.datamodel.SyncedFolderProvider @@ -52,6 +53,7 @@ import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.ui.activity.ManageAccountsActivity.PENDING_FOR_REMOVAL import com.owncloud.android.ui.activity.SyncedFoldersActivity import com.owncloud.android.ui.notifications.NotificationUtils +import com.owncloud.android.utils.SyncedFolderUtils import com.owncloud.android.utils.ThemeUtils import java.util.ArrayList import java.util.Random @@ -134,7 +136,9 @@ class MediaFoldersDetectionWork constructor( imageMediaFolder, user.toPlatformAccount() ) - if (folder == null) { + if (folder == null && + SyncedFolderUtils.isQualifyingMediaFolder(imageMediaFolder, MediaFolderType.IMAGE) + ) { val contentTitle = String.format( resources.getString(R.string.new_media_folder_detected), resources.getString(R.string.new_media_folder_photos) @@ -144,7 +148,7 @@ class MediaFoldersDetectionWork constructor( imageMediaFolder.substring(imageMediaFolder.lastIndexOf('/') + 1), user, imageMediaFolder, - 1 + MediaFolderType.IMAGE.id ) } } @@ -163,7 +167,7 @@ class MediaFoldersDetectionWork constructor( videoMediaFolder.substring(videoMediaFolder.lastIndexOf('/') + 1), user, videoMediaFolder, - 2 + MediaFolderType.VIDEO.id ) } } diff --git a/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java b/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java index 4f0579838fd4..2f6cbd140878 100644 --- a/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java @@ -71,6 +71,7 @@ import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.PermissionUtil; +import com.owncloud.android.utils.SyncedFolderUtils; import com.owncloud.android.utils.ThemeUtils; import java.io.File; @@ -378,13 +379,17 @@ private List mergeFolderData(List syncedF SyncedFolder syncedFolder = syncedFoldersMap.get(mediaFolder.absolutePath + "-" + mediaFolder.type); syncedFoldersMap.remove(mediaFolder.absolutePath + "-" + mediaFolder.type); - if (MediaFolderType.CUSTOM == syncedFolder.getType()) { - result.add(createSyncedFolderWithoutMediaFolder(syncedFolder)); - } else { - result.add(createSyncedFolder(syncedFolder, mediaFolder)); + if (syncedFolder != null && SyncedFolderUtils.isQualifyingMediaFolder(syncedFolder)) { + if (MediaFolderType.CUSTOM == syncedFolder.getType()) { + result.add(createSyncedFolderWithoutMediaFolder(syncedFolder)); + } else { + result.add(createSyncedFolder(syncedFolder, mediaFolder)); + } } } else { - result.add(createSyncedFolderFromMediaFolder(mediaFolder)); + if (SyncedFolderUtils.isQualifyingMediaFolder(mediaFolder)) { + result.add(createSyncedFolderFromMediaFolder(mediaFolder)); + } } } @@ -399,7 +404,7 @@ private List mergeFolderData(List syncedF private SyncedFolderDisplayItem createSyncedFolderWithoutMediaFolder(@NonNull SyncedFolder syncedFolder) { File localFolder = new File(syncedFolder.getLocalPath()); - File[] files = getFileList(localFolder); + File[] files = SyncedFolderUtils.getFileList(localFolder); List filePaths = getDisplayFilePathList(files); return new SyncedFolderDisplayItem( @@ -479,18 +484,6 @@ private SyncedFolderDisplayItem createSyncedFolderFromMediaFolder(@NonNull Media false); } - private File[] getFileList(File localFolder) { - File[] files = localFolder.listFiles(pathname -> !pathname.isDirectory()); - - if (files != null) { - Arrays.sort(files, (f1, f2) -> Long.compare(f1.lastModified(), f2.lastModified())); - } else { - files = new File[]{}; - } - - return files; - } - private List getDisplayFilePathList(File... files) { List filePaths = null; diff --git a/src/main/java/com/owncloud/android/utils/FileUtil.java b/src/main/java/com/owncloud/android/utils/FileUtil.java new file mode 100644 index 000000000000..ab0c0a2c8b0e --- /dev/null +++ b/src/main/java/com/owncloud/android/utils/FileUtil.java @@ -0,0 +1,47 @@ +/* + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2020 Andy Scherzinger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.owncloud.android.utils; + +import java.io.File; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public final class FileUtil { + private static final int EMPTY_LENGTH = 0; + + private FileUtil() { + // utility class -> private constructor + } + + public static @NonNull String getFilenameFromPathString(@Nullable String filePath) { + if (filePath != null && filePath.length() > EMPTY_LENGTH) { + File file = new File(filePath); + if (file.isFile()) { + return file.getName(); + } else { + return ""; + } + } else { + return ""; + } + } +} diff --git a/src/main/java/com/owncloud/android/utils/SyncedFolderUtils.java b/src/main/java/com/owncloud/android/utils/SyncedFolderUtils.java new file mode 100644 index 000000000000..d62c9d2cdd93 --- /dev/null +++ b/src/main/java/com/owncloud/android/utils/SyncedFolderUtils.java @@ -0,0 +1,202 @@ +/* + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2020 Andy Scherzinger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.owncloud.android.utils; + +import com.owncloud.android.datamodel.MediaFolder; +import com.owncloud.android.datamodel.MediaFolderType; +import com.owncloud.android.datamodel.SyncedFolder; + +import java.io.File; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +import androidx.annotation.Nullable; + +/** + * Utility class with methods for processing synced folders. + */ +public final class SyncedFolderUtils { + private static final String[] DISQUALIFIED_MEDIA_DETECTION_SOURCE = new String[]{ + "cover.jpg", "cover.jpeg", + "folder.jpg", "folder.jpeg" + }; + private static final Set DISQUALIFIED_MEDIA_DETECTION_SET = + new HashSet<>(Arrays.asList(DISQUALIFIED_MEDIA_DETECTION_SOURCE)); + private static final int SINGLE_FILE = 1; + + private SyncedFolderUtils() { + // utility class -> private constructor + } + + /** + * analyzes a given media folder if its content qualifies for the folder to be handled as a media folder. + * + * @param mediaFolder media folder to analyse + * @return true if it qualifies as a media folder else false + */ + public static boolean isQualifyingMediaFolder(@Nullable MediaFolder mediaFolder) { + if (mediaFolder == null) { + return false; + } + + // custom folders are always fine + if (MediaFolderType.CUSTOM == mediaFolder.type) { + return true; + } + + // filter media folders + + // no files + if (mediaFolder.numberOfFiles < SINGLE_FILE) { + return false; + } // music album (just one cover-art image) + else if (MediaFolderType.IMAGE == mediaFolder.type) { + return containsQualifiedImages(mediaFolder.filePaths); + } + + return true; + } + + /** + * analyzes a given synced folder if its content qualifies for the folder to be handled as a media folder. + * + * @param syncedFolder synced folder to analyse + * @return true if it qualifies as a media folder else false + */ + public static boolean isQualifyingMediaFolder(@Nullable SyncedFolder syncedFolder) { + if (syncedFolder == null) { + return false; + } + + // custom folders are always fine + if (MediaFolderType.CUSTOM == syncedFolder.getType()) { + return true; + } + + // filter media folders + File[] files = getFileList(new File(syncedFolder.getLocalPath())); + + // no files + if (files.length < SINGLE_FILE) { + return false; + } // music album (just one cover-art image) + else if (MediaFolderType.IMAGE == syncedFolder.getType()) { + return containsQualifiedImages(files); + } + + return true; + } + + /** + * analyzes a given folder based on a path-string and type if its content qualifies for the folder to be handled as + * a media folder. + * + * @param folderPath String representation for a folder + * @param folderType type of the folder + * @return true if it qualifies as a media folder else false + */ + public static boolean isQualifyingMediaFolder(String folderPath, MediaFolderType folderType) { + // custom folders are always fine + if (MediaFolderType.CUSTOM == folderType) { + return true; + } + + // filter media folders + File[] files = getFileList(new File(folderPath)); + + // no files + if (files.length < SINGLE_FILE) { + return false; + } // music album (just one cover-art image) + else if (MediaFolderType.IMAGE == folderType) { + return containsQualifiedImages(files); + } + + return true; + } + + /** + * check if given list contains images that qualify as auto upload relevant files. + * + * @param filePaths list of file paths + * @return true if at least one files qualifies as auto upload relevant else false + */ + private static boolean containsQualifiedImages(List filePaths) { + for (String filePath : filePaths) { + if (isFileNameQualifiedForMediaDetection(FileUtil.getFilenameFromPathString(filePath)) + && MimeTypeUtil.isImage(MimeTypeUtil.getMimeTypeFromPath(filePath))) { + return true; + } + } + + return false; + } + + /** + * check if given list of files contains images that qualify as auto upload relevant files. + * + * @param files list of files + * @return true if at least one files qualifies as auto upload relevant else false + */ + private static boolean containsQualifiedImages(File... files) { + for (File file : files) { + if (isFileNameQualifiedForMediaDetection(file.getName()) && MimeTypeUtil.isImage(file)) { + return true; + } + } + + return false; + } + + /** + * check if given file is auto upload relevant files. + * + * @param fileName file name to be checked + * @return true if the file qualifies as auto upload relevant else false + */ + public static boolean isFileNameQualifiedForMediaDetection(String fileName) { + if (fileName != null) { + return !DISQUALIFIED_MEDIA_DETECTION_SET.contains(fileName.toLowerCase(Locale.ROOT)); + } else { + return false; + } + } + + /** + * return list of files for given folder. + * + * @param localFolder folder to scan + * @return sorted list of folder of given folder + */ + public static File[] getFileList(File localFolder) { + File[] files = localFolder.listFiles(pathname -> !pathname.isDirectory()); + + if (files != null) { + Arrays.sort(files, (f1, f2) -> Long.compare(f1.lastModified(), f2.lastModified())); + } else { + files = new File[]{}; + } + + return files; + } +}