add exclude hidden file or folder option when create custom media folder type

Signed-off-by: JinWeiyang <jwy8645@163.com>
This commit is contained in:
JinWeiyang 2023-12-15 23:24:48 +08:00 committed by Andy Scherzinger
parent f7502cead3
commit 82fc3cf217
15 changed files with 182 additions and 31 deletions

View file

@ -74,7 +74,8 @@ public class SyncedFoldersActivityIT extends AbstractIT {
"Name",
MediaFolderType.IMAGE,
false,
SubFolderRule.YEAR_MONTH);
SubFolderRule.YEAR_MONTH,
false);
SyncedFolderPreferencesDialogFragment sut = SyncedFolderPreferencesDialogFragment.newInstance(item, 0);
Intent intent = new Intent(targetContext, SyncedFoldersActivity.class);

View file

@ -187,7 +187,8 @@ class SyncedFolderUtilsTest : AbstractIT() {
0L,
MediaFolderType.IMAGE,
false,
SubFolderRule.YEAR_MONTH
SubFolderRule.YEAR_MONTH,
false
)
Assert.assertFalse(SyncedFolderUtils.isQualifyingMediaFolder(folder))
}
@ -210,7 +211,8 @@ class SyncedFolderUtilsTest : AbstractIT() {
0L,
MediaFolderType.IMAGE,
false,
SubFolderRule.YEAR_MONTH
SubFolderRule.YEAR_MONTH,
false
)
Assert.assertFalse(SyncedFolderUtils.isQualifyingMediaFolder(folder))
}

View file

@ -59,5 +59,7 @@ data class SyncedFolderEntity(
@ColumnInfo(name = ProviderTableMeta.SYNCED_FOLDER_HIDDEN)
val hidden: Int?,
@ColumnInfo(name = ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_RULE)
val subFolderRule: Int?
val subFolderRule: Int?,
@ColumnInfo(name = ProviderTableMeta.SYNCED_EXCLUDE_HIDDEN)
val excludeHidden: Int?
)

View file

@ -51,6 +51,7 @@ public class SyncedFolder implements Serializable, Cloneable {
private MediaFolderType type;
private boolean hidden;
private SubFolderRule subfolderRule;
private boolean excludeHidden;
/**
* constructor for new, to be persisted entity.
@ -68,6 +69,8 @@ public class SyncedFolder implements Serializable, Cloneable {
* @param timestampMs the current timestamp in milliseconds
* @param type the type of the folder
* @param hidden hide item flag
* @param subFolderRule whether to filter subFolder by year/month/day
* @param excludeHidden exclude hidden file or folder, for {@link MediaFolderType#CUSTOM} only
*/
public SyncedFolder(String localPath,
String remotePath,
@ -82,7 +85,8 @@ public class SyncedFolder implements Serializable, Cloneable {
long timestampMs,
MediaFolderType type,
boolean hidden,
SubFolderRule subFolderRule) {
SubFolderRule subFolderRule,
boolean excludeHidden) {
this(UNPERSISTED_ID,
localPath,
remotePath,
@ -97,7 +101,8 @@ public class SyncedFolder implements Serializable, Cloneable {
timestampMs,
type,
hidden,
subFolderRule);
subFolderRule,
excludeHidden);
}
/**
@ -119,7 +124,8 @@ public class SyncedFolder implements Serializable, Cloneable {
long timestampMs,
MediaFolderType type,
boolean hidden,
SubFolderRule subFolderRule) {
SubFolderRule subFolderRule,
boolean excludeHidden) {
this.id = id;
this.localPath = localPath;
this.remotePath = remotePath;
@ -134,6 +140,7 @@ public class SyncedFolder implements Serializable, Cloneable {
this.type = type;
this.hidden = hidden;
this.subfolderRule = subFolderRule;
this.excludeHidden = excludeHidden;
}
/**
@ -263,4 +270,12 @@ public class SyncedFolder implements Serializable, Cloneable {
}
public void setSubFolderRule(SubFolderRule subFolderRule) { this.subfolderRule = subFolderRule; }
public boolean isExcludeHidden() {
return excludeHidden;
}
public void setExcludeHidden(boolean excludeHidden) {
this.excludeHidden = excludeHidden;
}
}

View file

@ -54,6 +54,7 @@ public class SyncedFolderDisplayItem extends SyncedFolder {
* @param type the type of the folder
* @param hidden hide item flag
* @param subFolderRule whether to filter subFolder by year/month/day
* @param excludeHidden exclude hidden file or folder, for {@link MediaFolderType#CUSTOM} only
*/
public SyncedFolderDisplayItem(long id,
String localPath,
@ -72,7 +73,8 @@ public class SyncedFolderDisplayItem extends SyncedFolder {
long numberOfFiles,
MediaFolderType type,
boolean hidden,
SubFolderRule subFolderRule) {
SubFolderRule subFolderRule,
boolean excludeHidden) {
super(id,
localPath,
remotePath,
@ -87,7 +89,8 @@ public class SyncedFolderDisplayItem extends SyncedFolder {
timestampMs,
type,
hidden,
subFolderRule);
subFolderRule,
excludeHidden);
this.filePaths = filePaths;
this.folderName = folderName;
this.numberOfFiles = numberOfFiles;
@ -108,7 +111,8 @@ public class SyncedFolderDisplayItem extends SyncedFolder {
String folderName,
MediaFolderType type,
boolean hidden,
SubFolderRule subFolderRule) {
SubFolderRule subFolderRule,
boolean excludeHidden) {
super(id,
localPath,
remotePath,
@ -123,7 +127,8 @@ public class SyncedFolderDisplayItem extends SyncedFolder {
timestampMs,
type,
hidden,
subFolderRule);
subFolderRule,
excludeHidden);
this.folderName = folderName;
}

View file

@ -372,6 +372,8 @@ public class SyncedFolderProvider extends Observable {
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_HIDDEN)) == 1;
SubFolderRule subFolderRule = SubFolderRule.values()[cursor.getInt(
cursor.getColumnIndexOrThrow(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_RULE))];
boolean excludeHidden = cursor.getInt(cursor.getColumnIndexOrThrow(
ProviderMeta.ProviderTableMeta.SYNCED_EXCLUDE_HIDDEN)) == 1;
syncedFolder = new SyncedFolder(id,
@ -388,7 +390,8 @@ public class SyncedFolderProvider extends Observable {
enabledTimestampMs,
type,
hidden,
subFolderRule);
subFolderRule,
excludeHidden);
}
return syncedFolder;
}
@ -417,6 +420,7 @@ public class SyncedFolderProvider extends Observable {
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE, syncedFolder.getType().id);
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_HIDDEN, syncedFolder.isHidden());
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_RULE, syncedFolder.getSubfolderRule().ordinal());
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_EXCLUDE_HIDDEN, syncedFolder.isExcludeHidden());
return cv;
}

View file

@ -302,6 +302,7 @@ public class ProviderMeta {
public static final String SYNCED_FOLDER_NAME_COLLISION_POLICY = "name_collision_policy";
public static final String SYNCED_FOLDER_HIDDEN = "hidden";
public static final String SYNCED_FOLDER_SUBFOLDER_RULE = "sub_folder_rule";
public static final String SYNCED_EXCLUDE_HIDDEN = "exclude_hidden";
// Columns of external links table
public static final String EXTERNAL_LINKS_ICON_URL = "icon_url";

View file

@ -403,7 +403,8 @@ class SyncedFoldersActivity :
files.size.toLong(),
syncedFolder.type,
syncedFolder.isHidden,
syncedFolder.subfolderRule
syncedFolder.subfolderRule,
syncedFolder.isExcludeHidden
)
}
@ -433,7 +434,8 @@ class SyncedFoldersActivity :
mediaFolder.numberOfFiles,
mediaFolder.type,
syncedFolder.isHidden,
syncedFolder.subfolderRule
syncedFolder.subfolderRule,
syncedFolder.isExcludeHidden
)
}
@ -462,7 +464,8 @@ class SyncedFoldersActivity :
mediaFolder.numberOfFiles,
mediaFolder.type,
false,
SubFolderRule.YEAR_MONTH
SubFolderRule.YEAR_MONTH,
false
)
}
@ -554,7 +557,8 @@ class SyncedFoldersActivity :
null,
MediaFolderType.CUSTOM,
false,
SubFolderRule.YEAR_MONTH
SubFolderRule.YEAR_MONTH,
false
)
onSyncFolderSettingsClick(0, emptyCustomFolder)
} else {
@ -670,7 +674,8 @@ class SyncedFoldersActivity :
File(syncedFolder.localPath).name,
syncedFolder.type,
syncedFolder.isHidden,
syncedFolder.subFolderRule
syncedFolder.subFolderRule,
syncedFolder.isExcludeHidden
)
saveOrUpdateSyncedFolder(newCustomFolder)
adapter.addSyncFolderItem(newCustomFolder)
@ -688,7 +693,8 @@ class SyncedFoldersActivity :
syncedFolder.uploadAction,
syncedFolder.nameCollisionPolicy.serialize(),
syncedFolder.isEnabled,
syncedFolder.subFolderRule
syncedFolder.subFolderRule,
syncedFolder.isExcludeHidden
)
saveOrUpdateSyncedFolder(item)
@ -759,6 +765,7 @@ class SyncedFoldersActivity :
* @param uploadAction upload action
* @param nameCollisionPolicy what to do on name collision
* @param enabled is sync enabled
* @param excludeHidden exclude hidden file or folder, for {@link MediaFolderType#CUSTOM} only
*/
@Suppress("LongParameterList")
private fun updateSyncedFolderItem(
@ -773,7 +780,8 @@ class SyncedFoldersActivity :
uploadAction: Int,
nameCollisionPolicy: Int,
enabled: Boolean,
subFolderRule: SubFolderRule
subFolderRule: SubFolderRule,
excludeHidden: Boolean
) {
item.id = id
item.localPath = localPath
@ -786,6 +794,7 @@ class SyncedFoldersActivity :
item.setNameCollisionPolicy(nameCollisionPolicy)
item.setEnabled(enabled, clock.currentTime)
item.setSubFolderRule(subFolderRule)
item.setExcludeHidden(excludeHidden)
}
override fun onRequestPermissionsResult(

View file

@ -130,12 +130,16 @@ class SyncedFolderPreferencesDialogFragment : DialogFragment(), Injectable {
// hide local folder chooser and delete for non-custom folders
binding.localFolderContainer.visibility = View.GONE
isNeutralButtonActive = false
binding.settingInstantUploadExcludeHiddenContainer.visibility = View.GONE
} else if (syncedFolder!!.id <= SyncedFolder.UNPERSISTED_ID) {
isNeutralButtonActive = false
// Hide delete/enabled for unpersisted custom folders
binding.syncEnabled.visibility = View.GONE
// Show exclude hidden checkbox when {@link MediaFolderType#CUSTOM}
binding.settingInstantUploadExcludeHiddenContainer.visibility = View.VISIBLE
// auto set custom folder to enabled
syncedFolder?.isEnabled = true
@ -146,6 +150,10 @@ class SyncedFolderPreferencesDialogFragment : DialogFragment(), Injectable {
binding.btnPositive.isEnabled = false
} else {
binding.localFolderContainer.visibility = View.GONE
if (MediaFolderType.CUSTOM.id == syncedFolder!!.type.id) {
// Show exclude hidden checkbox when {@link MediaFolderType#CUSTOM}
binding.settingInstantUploadExcludeHiddenContainer.visibility = View.VISIBLE
}
}
}
@ -156,7 +164,8 @@ class SyncedFolderPreferencesDialogFragment : DialogFragment(), Injectable {
binding.settingInstantUploadOnWifiCheckbox,
binding.settingInstantUploadOnChargingCheckbox,
binding.settingInstantUploadExistingCheckbox,
binding.settingInstantUploadPathUseSubfoldersCheckbox
binding.settingInstantUploadPathUseSubfoldersCheckbox,
binding.settingInstantUploadExcludeHiddenCheckbox
)
viewThemeUtils?.material?.colorMaterialButtonPrimaryTonal(binding.btnPositive)
@ -209,6 +218,7 @@ class SyncedFolderPreferencesDialogFragment : DialogFragment(), Injectable {
binding.settingInstantUploadOnChargingCheckbox.isChecked = it.isChargingOnly
binding.settingInstantUploadExistingCheckbox.isChecked = it.isExisting
binding.settingInstantUploadPathUseSubfoldersCheckbox.isChecked = it.isSubfolderByDate
binding.settingInstantUploadExcludeHiddenCheckbox.isChecked = it.isExcludeHidden
binding.settingInstantUploadSubfolderRuleSpinner.setSelection(it.subFolderRule.ordinal)
@ -311,6 +321,8 @@ class SyncedFolderPreferencesDialogFragment : DialogFragment(), Injectable {
binding.settingInstantUploadExistingContainer.alpha = alpha
binding.settingInstantUploadPathUseSubfoldersContainer.isEnabled = enable
binding.settingInstantUploadPathUseSubfoldersContainer.alpha = alpha
binding.settingInstantUploadExcludeHiddenContainer.isEnabled = enable
binding.settingInstantUploadExcludeHiddenContainer.alpha = alpha
binding.remoteFolderContainer.isEnabled = enable
binding.remoteFolderContainer.alpha = alpha
binding.localFolderContainer.isEnabled = enable
@ -321,6 +333,7 @@ class SyncedFolderPreferencesDialogFragment : DialogFragment(), Injectable {
binding.settingInstantUploadOnChargingCheckbox.isEnabled = enable
binding.settingInstantUploadExistingCheckbox.isEnabled = enable
binding.settingInstantUploadPathUseSubfoldersCheckbox.isEnabled = enable
binding.settingInstantUploadExcludeHiddenCheckbox.isEnabled = enable
}
checkWritableFolder()
@ -364,6 +377,10 @@ class SyncedFolderPreferencesDialogFragment : DialogFragment(), Injectable {
binding.settingInstantUploadSubfolderRuleContainer.visibility = View.GONE
}
}
binding.settingInstantUploadExcludeHiddenContainer.setOnClickListener {
syncedFolder.isExcludeHidden = !syncedFolder.isExcludeHidden
binding.settingInstantUploadExcludeHiddenCheckbox.toggle()
}
binding.settingInstantUploadSubfolderRuleSpinner.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(adapterView: AdapterView<*>?, view: View, i: Int, l: Long) {

View file

@ -49,6 +49,7 @@ public class SyncedFolderParcelable implements Parcelable {
private String account;
private int section;
private SubFolderRule subFolderRule;
private boolean excludeHidden;
public SyncedFolderParcelable(SyncedFolderDisplayItem syncedFolderDisplayItem, int section) {
id = syncedFolderDisplayItem.getId();
@ -68,6 +69,7 @@ public class SyncedFolderParcelable implements Parcelable {
this.section = section;
hidden = syncedFolderDisplayItem.isHidden();
subFolderRule = syncedFolderDisplayItem.getSubfolderRule();
excludeHidden = syncedFolderDisplayItem.isExcludeHidden();
}
private SyncedFolderParcelable(Parcel read) {
@ -87,6 +89,7 @@ public class SyncedFolderParcelable implements Parcelable {
section = read.readInt();
hidden = read.readInt() != 0;
subFolderRule = SubFolderRule.values()[read.readInt()];
excludeHidden = read.readInt() != 0;
}
public SyncedFolderParcelable() {
@ -111,6 +114,7 @@ public class SyncedFolderParcelable implements Parcelable {
dest.writeInt(section);
dest.writeInt(hidden ? 1 : 0);
dest.writeInt(subFolderRule.ordinal());
dest.writeInt(excludeHidden ? 1 : 0);
}
public static final Creator<SyncedFolderParcelable> CREATOR =
@ -279,4 +283,12 @@ public class SyncedFolderParcelable implements Parcelable {
this.section = section;
}
public void setSubFolderRule(SubFolderRule subFolderRule) { this.subFolderRule = subFolderRule; }
public boolean isExcludeHidden() {
return excludeHidden;
}
public void setExcludeHidden(boolean excludeHidden) {
this.excludeHidden = excludeHidden;
}
}

View file

@ -26,6 +26,11 @@ import android.text.TextUtils;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.files.UploadFileRemoteOperation;
import org.lukhnos.nnio.file.FileVisitResult;
import org.lukhnos.nnio.file.FileVisitor;
import org.lukhnos.nnio.file.Path;
import org.lukhnos.nnio.file.impl.FileBasedPathImpl;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@ -77,4 +82,19 @@ public final class FileUtil {
return null;
}
}
public static Path walkFileTree(Path start, FileVisitor<? super Path> visitor) throws IOException {
if (org.lukhnos.nnio.file.Files.isDirectory(start)) {
org.lukhnos.nnio.file.FileVisitResult preVisitDirectoryResult = visitor.preVisitDirectory(start, null);
if (preVisitDirectoryResult == FileVisitResult.CONTINUE) {
for (File child : start.toFile().listFiles()) {
walkFileTree(FileBasedPathImpl.get(child), visitor);
}
}
visitor.postVisitDirectory(start, null);
} else {
visitor.visitFile(start, new org.lukhnos.nnio.file.attribute.BasicFileAttributes(start.toFile()));
}
return start;
}
}

View file

@ -48,7 +48,6 @@ import com.owncloud.android.db.UploadResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import org.lukhnos.nnio.file.FileVisitResult;
import org.lukhnos.nnio.file.Files;
import org.lukhnos.nnio.file.Path;
import org.lukhnos.nnio.file.Paths;
import org.lukhnos.nnio.file.SimpleFileVisitor;
@ -94,10 +93,14 @@ public final class FilesSyncHelper {
FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
Path path = Paths.get(syncedFolder.getLocalPath());
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
FileUtil.walkFileTree(path, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
File file = path.toFile();
if (syncedFolder.isExcludeHidden() && file.isHidden()) {
// exclude hidden file or folder
return FileVisitResult.CONTINUE;
}
if (syncedFolder.isExisting() || attrs.lastModifiedTime().toMillis() >= enabledTimestampMs) {
filesystemDataProvider.storeOrUpdateFileValue(path.toAbsolutePath().toString(),
attrs.lastModifiedTime().toMillis(),
@ -107,6 +110,14 @@ public final class FilesSyncHelper {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
if (syncedFolder.isExcludeHidden() && dir.compareTo(Paths.get(syncedFolder.getLocalPath())) != 0 && dir.toFile().isHidden()) {
return null;
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
return FileVisitResult.CONTINUE;
@ -186,11 +197,10 @@ public final class FilesSyncHelper {
for (OCUpload failedUpload : failedUploads) {
accountExists = false;
if(!failedUpload.isWhileChargingOnly()){
if (!failedUpload.isWhileChargingOnly()) {
whileChargingOnly = false;
}
if(!failedUpload.isUseWifiOnly())
{
if (!failedUpload.isUseWifiOnly()) {
useWifiOnly = false;
}
@ -208,22 +218,21 @@ public final class FilesSyncHelper {
}
failedUploads = uploadsStorageManager.getFailedUploads();
if(failedUploads.length == 0)
{
if (failedUploads.length == 0) {
//nothing to do
return;
}
if(whileChargingOnly){
if (whileChargingOnly) {
final BatteryStatus batteryStatus = powerManagementService.getBattery();
final boolean charging = batteryStatus.isCharging() || batteryStatus.isFull();
if(!charging){
if (!charging) {
//all uploads requires charging
return;
}
}
if (useWifiOnly && !connectivityService.getConnectivity().isWifi()){
if (useWifiOnly && !connectivityService.getConnectivity().isWifi()) {
//all uploads requires wifi
return;
}

View file

@ -305,6 +305,57 @@
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/setting_instant_upload_exclude_hidden_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false">
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="@dimen/standard_padding">
<TextView
android:id="@+id/setting_instant_upload_exclude_hidden_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:maxLines="2"
android:text="@string/prefs_instant_upload_exclude_hidden_title"
android:textAppearance="?attr/textAppearanceListItem" />
<TextView
android:id="@+id/setting_instant_upload_exclude_hidden_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/setting_instant_upload_exclude_hidden_label"
android:layout_alignStart="@id/setting_instant_upload_exclude_hidden_label"
android:ellipsize="end"
android:maxLines="2"
android:text="@string/prefs_instant_upload_exclude_hidden_summary"
android:textColor="?android:attr/textColorSecondary" />
</RelativeLayout>
<!-- Preference should place its actual preference widget here. -->
<LinearLayout
android:id="@+id/setting_instant_upload_exclude_hidden_frame"
android:layout_width="@dimen/synced_folders_control_width"
android:layout_height="match_parent"
android:gravity="center"
android:padding="@dimen/standard_padding">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/setting_instant_upload_exclude_hidden_checkbox"
android:layout_width="32dp"
android:layout_height="wrap_content"
android:background="@null"
android:clickable="false"
android:focusable="false" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/setting_instant_upload_subfolder_rule_container"
android:layout_width="match_parent"

View file

@ -368,6 +368,8 @@
<string name="prefs_synced_folders_remote_path_title">Remote folder</string>
<string name="prefs_instant_upload_path_use_subfolders_title">Use subfolders</string>
<string name="prefs_instant_upload_path_use_date_subfolders_summary">Store in subfolders based on date</string>
<string name="prefs_instant_upload_exclude_hidden_title">Exclude hidden</string>
<string name="prefs_instant_upload_exclude_hidden_summary">Exclude hidden file or folder</string>
<string name="prefs_instant_upload_subfolder_rule_title">Subfolder options</string>

View file

@ -178,6 +178,7 @@ public class SyncedFoldersActivityTest {
2,
MediaFolderType.IMAGE,
false,
SubFolderRule.YEAR_MONTH);
SubFolderRule.YEAR_MONTH,
true);
}
}