mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-22 20:24:03 -04:00
Merge branch 'develop' into iadeelzafar/wifi-hotspot
This commit is contained in:
commit
7972774e2f
43
app/src/main/java/eu/mhutti1/utils/storage/Bytes.kt
Normal file
43
app/src/main/java/eu/mhutti1/utils/storage/Bytes.kt
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Kiwix Android
|
||||||
|
* Copyright (C) 2018 Kiwix <android.kiwix.org>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package eu.mhutti1.utils.storage
|
||||||
|
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
|
const val Kb = 1 * 1024L
|
||||||
|
const val Mb = Kb * 1024
|
||||||
|
const val Gb = Mb * 1024
|
||||||
|
const val Tb = Gb * 1024
|
||||||
|
const val Pb = Tb * 1024
|
||||||
|
const val Eb = Pb * 1024
|
||||||
|
|
||||||
|
inline class Bytes(val size: Long) {
|
||||||
|
val humanReadable
|
||||||
|
get() = when {
|
||||||
|
size < Kb -> "${floatForm(size)} byte"
|
||||||
|
size < Mb -> "${floatForm(size / Kb)} KB"
|
||||||
|
size < Gb -> "${floatForm(size / Mb)} MB"
|
||||||
|
size < Tb -> "${floatForm(size / Gb)} GB"
|
||||||
|
size < Pb -> "${floatForm(size / Tb)} TB"
|
||||||
|
size < Eb -> "${floatForm(size / Pb)} PB"
|
||||||
|
size >= Eb -> "${floatForm(size / Eb)} EB"
|
||||||
|
else -> "???"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun floatForm(d: Long) = DecimalFormat("#.#").format(d.toDouble())
|
||||||
|
}
|
@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Isaac Hutt <mhutti1@gmail.com>
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 3 of the License, or
|
|
||||||
* 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 General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
||||||
* MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package eu.mhutti1.utils.storage;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
|
|
||||||
class ExternalPaths {
|
|
||||||
|
|
||||||
@SuppressLint("SdCardPath") private static final String[] paths = {
|
|
||||||
"/storage/sdcard0",
|
|
||||||
"/storage/sdcard1",
|
|
||||||
"/storage/extsdcard",
|
|
||||||
"/storage/extSdCard",
|
|
||||||
"/storage/sdcard0/external_sdcard",
|
|
||||||
"/mnt/sdcard/external_sd",
|
|
||||||
"/mnt/external_sd",
|
|
||||||
"/mnt/media_rw/*",
|
|
||||||
"/removable/microsd",
|
|
||||||
"/mnt/emmc",
|
|
||||||
"/storage/external_SD",
|
|
||||||
"/storage/ext_sd",
|
|
||||||
"/storage/removable/sdcard1",
|
|
||||||
"/data/sdext",
|
|
||||||
"/data/sdext2",
|
|
||||||
"/data/sdext3",
|
|
||||||
"/data/sdext2",
|
|
||||||
"/data/sdext3",
|
|
||||||
"/data/sdext4",
|
|
||||||
"/sdcard",
|
|
||||||
"/sdcard1",
|
|
||||||
"/sdcard2",
|
|
||||||
"/storage/microsd",
|
|
||||||
"/mnt/extsd",
|
|
||||||
"/extsd",
|
|
||||||
"/mnt/sdcard",
|
|
||||||
"/misc/android",
|
|
||||||
};
|
|
||||||
|
|
||||||
public static String[] getPossiblePaths() {
|
|
||||||
return paths;
|
|
||||||
}
|
|
||||||
}
|
|
56
app/src/main/java/eu/mhutti1/utils/storage/ExternalPaths.kt
Normal file
56
app/src/main/java/eu/mhutti1/utils/storage/ExternalPaths.kt
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Isaac Hutt <mhutti1@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 3 of the License, or
|
||||||
|
* 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
* MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package eu.mhutti1.utils.storage
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
|
||||||
|
internal object ExternalPaths {
|
||||||
|
|
||||||
|
@SuppressLint("SdCardPath")
|
||||||
|
val possiblePaths = arrayOf(
|
||||||
|
"/storage/sdcard0",
|
||||||
|
"/storage/sdcard1",
|
||||||
|
"/storage/extsdcard",
|
||||||
|
"/storage/extSdCard",
|
||||||
|
"/storage/sdcard0/external_sdcard",
|
||||||
|
"/mnt/sdcard/external_sd",
|
||||||
|
"/mnt/external_sd",
|
||||||
|
"/mnt/media_rw/*",
|
||||||
|
"/removable/microsd",
|
||||||
|
"/mnt/emmc",
|
||||||
|
"/storage/external_SD",
|
||||||
|
"/storage/ext_sd",
|
||||||
|
"/storage/removable/sdcard1",
|
||||||
|
"/data/sdext",
|
||||||
|
"/data/sdext2",
|
||||||
|
"/data/sdext3",
|
||||||
|
"/data/sdext2",
|
||||||
|
"/data/sdext3",
|
||||||
|
"/data/sdext4",
|
||||||
|
"/sdcard",
|
||||||
|
"/sdcard1",
|
||||||
|
"/sdcard2",
|
||||||
|
"/storage/microsd",
|
||||||
|
"/mnt/extsd",
|
||||||
|
"/extsd",
|
||||||
|
"/mnt/sdcard",
|
||||||
|
"/misc/android"
|
||||||
|
)
|
||||||
|
}
|
@ -1,164 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Isaac Hutt <mhutti1@gmail.com>
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 3 of the License, or
|
|
||||||
* 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 General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
||||||
* MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package eu.mhutti1.utils.storage;
|
|
||||||
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.StatFs;
|
|
||||||
import android.util.Log;
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.text.DecimalFormat;
|
|
||||||
|
|
||||||
public class StorageDevice {
|
|
||||||
|
|
||||||
// File object containing device path
|
|
||||||
private final File mFile;
|
|
||||||
|
|
||||||
private final boolean mInternal;
|
|
||||||
|
|
||||||
private boolean mDuplicate = true;
|
|
||||||
|
|
||||||
public StorageDevice(String path, boolean internal) {
|
|
||||||
mFile = new File(path);
|
|
||||||
mInternal = internal;
|
|
||||||
if (mFile.exists()) {
|
|
||||||
createLocationCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public StorageDevice(File file, boolean internal) {
|
|
||||||
mFile = file;
|
|
||||||
mInternal = internal;
|
|
||||||
if (mFile.exists()) {
|
|
||||||
createLocationCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get device path
|
|
||||||
public String getName() {
|
|
||||||
return mFile.getPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get available space on device
|
|
||||||
public String getSize() {
|
|
||||||
return bytesToHuman(getAvailableBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Long getAvailableBytes() {
|
|
||||||
StatFs statFs = new StatFs(mFile.getPath());
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
|
||||||
return statFs.getBlockSizeLong() * statFs.getAvailableBlocksLong();
|
|
||||||
} else {
|
|
||||||
return (long) statFs.getBlockSize() * (long) statFs.getAvailableBlocks();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTotalSize() {
|
|
||||||
return bytesToHuman(getTotalBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get total space on device
|
|
||||||
private Long getTotalBytes() {
|
|
||||||
StatFs statFs = new StatFs((mFile.getPath()));
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
|
||||||
return statFs.getBlockSizeLong() * statFs.getBlockCountLong();
|
|
||||||
} else {
|
|
||||||
return (long) statFs.getBlockSize() * (long) statFs.getBlockCount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert bytes to human readable form
|
|
||||||
private static String bytesToHuman(long size) {
|
|
||||||
long Kb = 1 * 1024;
|
|
||||||
long Mb = Kb * 1024;
|
|
||||||
long Gb = Mb * 1024;
|
|
||||||
long Tb = Gb * 1024;
|
|
||||||
long Pb = Tb * 1024;
|
|
||||||
long Eb = Pb * 1024;
|
|
||||||
|
|
||||||
if (size < Kb) return floatForm(size) + " byte";
|
|
||||||
if (size >= Kb && size < Mb) return floatForm((double) size / Kb) + " KB";
|
|
||||||
if (size >= Mb && size < Gb) return floatForm((double) size / Mb) + " MB";
|
|
||||||
if (size >= Gb && size < Tb) return floatForm((double) size / Gb) + " GB";
|
|
||||||
if (size >= Tb && size < Pb) return floatForm((double) size / Tb) + " TB";
|
|
||||||
if (size >= Pb && size < Eb) return floatForm((double) size / Pb) + " PB";
|
|
||||||
if (size >= Eb) return floatForm((double) size / Eb) + " EB";
|
|
||||||
|
|
||||||
return "???";
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInternal() {
|
|
||||||
return mInternal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public File getPath() {
|
|
||||||
return mFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String floatForm(double d) {
|
|
||||||
return new DecimalFormat("#.#").format(d);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create unique file to identify duplicate devices.
|
|
||||||
private void createLocationCode() {
|
|
||||||
if (!getLocationCodeFromFolder(mFile)) {
|
|
||||||
File locationCode = new File(mFile.getPath(), ".storageLocationMarker");
|
|
||||||
try {
|
|
||||||
locationCode.createNewFile();
|
|
||||||
FileWriter fw = new FileWriter(locationCode);
|
|
||||||
fw.write(mFile.getPath());
|
|
||||||
fw.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.d("android-storage-devices", "Unable to create marker file, duplicates may be listed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if there is already a device code in our path
|
|
||||||
private boolean getLocationCodeFromFolder(File folder) {
|
|
||||||
File locationCode = new File(folder.getPath(), ".storageLocationMarker");
|
|
||||||
if (locationCode.exists()) {
|
|
||||||
try ( BufferedReader br = new BufferedReader(new FileReader(locationCode))){
|
|
||||||
if (br.readLine().equals(mFile.getPath())) {
|
|
||||||
mDuplicate = false;
|
|
||||||
} else {
|
|
||||||
mDuplicate = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String path = folder.getPath();
|
|
||||||
String parent = path.substring(0, path.lastIndexOf("/"));
|
|
||||||
if (parent.equals("")) {
|
|
||||||
mDuplicate = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return getLocationCodeFromFolder(new File(parent));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDuplicate() {
|
|
||||||
return mDuplicate;
|
|
||||||
}
|
|
||||||
}
|
|
107
app/src/main/java/eu/mhutti1/utils/storage/StorageDevice.kt
Normal file
107
app/src/main/java/eu/mhutti1/utils/storage/StorageDevice.kt
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Isaac Hutt <mhutti1@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 3 of the License, or
|
||||||
|
* 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
* MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package eu.mhutti1.utils.storage
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.StatFs
|
||||||
|
import java.io.BufferedReader
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileReader
|
||||||
|
import java.io.FileWriter
|
||||||
|
|
||||||
|
const val LOCATION_EXTENSION = "storageLocationMarker"
|
||||||
|
|
||||||
|
data class StorageDevice(
|
||||||
|
val file: File,
|
||||||
|
val isInternal: Boolean
|
||||||
|
) {
|
||||||
|
|
||||||
|
constructor(path: String, internal: Boolean) : this(File(path), internal)
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (file.exists()) {
|
||||||
|
createLocationCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isDuplicate = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
val name: String
|
||||||
|
get() = file.path
|
||||||
|
|
||||||
|
val availableSpace: String
|
||||||
|
get() = Bytes(availableBytes).humanReadable
|
||||||
|
|
||||||
|
private val availableBytes: Long
|
||||||
|
get() = StatFs(file.path).let {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||||
|
it.blockSizeLong * it.availableBlocksLong
|
||||||
|
else
|
||||||
|
it.blockSize.toLong() * it.availableBlocks.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
val totalSize: String
|
||||||
|
get() = Bytes(totalBytes).humanReadable
|
||||||
|
|
||||||
|
// Get total space on device
|
||||||
|
private val totalBytes: Long
|
||||||
|
get() = StatFs(file.path).let {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||||
|
it.blockSizeLong * it.blockCountLong
|
||||||
|
else
|
||||||
|
it.blockSize.toLong() * it.blockCount.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create unique file to identify duplicate devices.
|
||||||
|
private fun createLocationCode() {
|
||||||
|
if (!getLocationCodeFromFolder(file)) {
|
||||||
|
File(file.path, ".$LOCATION_EXTENSION").let { locationCode ->
|
||||||
|
locationCode.createNewFile()
|
||||||
|
FileWriter(locationCode).use { it.write(file.path) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there is already a device code in our path
|
||||||
|
private fun getLocationCodeFromFolder(folder: File): Boolean {
|
||||||
|
val locationCode = File(folder.path, ".$LOCATION_EXTENSION")
|
||||||
|
if (locationCode.exists()) {
|
||||||
|
try {
|
||||||
|
BufferedReader(FileReader(locationCode)).use { br ->
|
||||||
|
if (br.readLine() == file.path) {
|
||||||
|
isDuplicate = false
|
||||||
|
} else {
|
||||||
|
isDuplicate = true
|
||||||
|
return@getLocationCodeFromFolder true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val parent = folder.parentFile
|
||||||
|
if (parent == null) {
|
||||||
|
isDuplicate = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return getLocationCodeFromFolder(parent)
|
||||||
|
}
|
||||||
|
}
|
@ -1,131 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Isaac Hutt <mhutti1@gmail.com>
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 3 of the License, or
|
|
||||||
* 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 General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
||||||
* MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package eu.mhutti1.utils.storage;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Environment;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.RandomAccessFile;
|
|
||||||
import java.nio.channels.FileChannel;
|
|
||||||
import java.nio.channels.FileLock;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
public class StorageDeviceUtils {
|
|
||||||
|
|
||||||
public static ArrayList<StorageDevice> getStorageDevices(Context context, boolean writable) {
|
|
||||||
ArrayList<StorageDevice> storageDevices = new ArrayList<>();
|
|
||||||
|
|
||||||
// Add as many possible mount points as we know about
|
|
||||||
|
|
||||||
// Only add this device if its very likely that we have missed a users sd card
|
|
||||||
if (Environment.isExternalStorageEmulated()) {
|
|
||||||
// This is our internal storage directory
|
|
||||||
storageDevices.add(new StorageDevice(
|
|
||||||
generalisePath(Environment.getExternalStorageDirectory().getPath(), writable), true));
|
|
||||||
} else {
|
|
||||||
// This is an external storage directory
|
|
||||||
storageDevices.add(new StorageDevice(
|
|
||||||
generalisePath(Environment.getExternalStorageDirectory().getPath(), writable), false));
|
|
||||||
}
|
|
||||||
|
|
||||||
// These are possible manufacturer sdcard mount points
|
|
||||||
|
|
||||||
String[] paths = ExternalPaths.getPossiblePaths();
|
|
||||||
|
|
||||||
for (String path : paths) {
|
|
||||||
if (path.endsWith("*")) {
|
|
||||||
File root = new File(path.substring(0, path.length() - 1));
|
|
||||||
File[] directories = root.listFiles(file -> file.isDirectory());
|
|
||||||
if (directories != null) {
|
|
||||||
for (File dir : directories) {
|
|
||||||
storageDevices.add(new StorageDevice(dir, false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
storageDevices.add(new StorageDevice(path, false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate through any sdcards manufacturers may have specified
|
|
||||||
for (File file : ContextCompat.getExternalFilesDirs(context, "")) {
|
|
||||||
if (file != null) {
|
|
||||||
storageDevices.add(new StorageDevice(generalisePath(file.getPath(), writable), false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check all devices exist, we can write to them if required and they are not duplicates
|
|
||||||
return checkStorageValid(writable, storageDevices);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove app specific path from directories so that we can search them from the top
|
|
||||||
private static String generalisePath(String path, boolean writable) {
|
|
||||||
if (writable) {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
int endIndex = path.lastIndexOf("/Android/data/");
|
|
||||||
if (endIndex != -1) {
|
|
||||||
return path.substring(0, endIndex);
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ArrayList<StorageDevice> checkStorageValid(boolean writable,
|
|
||||||
ArrayList<StorageDevice> storageDevices) {
|
|
||||||
ArrayList<StorageDevice> activeDevices = new ArrayList<>();
|
|
||||||
ArrayList<StorageDevice> devicePaths = new ArrayList<>();
|
|
||||||
for (StorageDevice device : storageDevices) {
|
|
||||||
if (existsAndIsDirAndWritableIfRequiredAndNotDuplicate(writable, devicePaths, device)) {
|
|
||||||
activeDevices.add(device);
|
|
||||||
devicePaths.add(device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return activeDevices;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean existsAndIsDirAndWritableIfRequiredAndNotDuplicate(boolean writable,
|
|
||||||
ArrayList<StorageDevice> devicePaths, StorageDevice device) {
|
|
||||||
final File devicePath = device.getPath();
|
|
||||||
return devicePath.exists()
|
|
||||||
&& devicePath.isDirectory()
|
|
||||||
&& (canWrite(devicePath) || !writable)
|
|
||||||
&& !device.isDuplicate()
|
|
||||||
&& !devicePaths.contains(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Amazingly file.canWrite() does not always return the correct value
|
|
||||||
private static boolean canWrite(File file) {
|
|
||||||
final String filePath = file + "/test.txt";
|
|
||||||
try {
|
|
||||||
RandomAccessFile randomAccessFile = new RandomAccessFile(filePath, "rw");
|
|
||||||
FileChannel fileChannel = randomAccessFile.getChannel();
|
|
||||||
FileLock fileLock = fileChannel.lock();
|
|
||||||
fileLock.release();
|
|
||||||
fileChannel.close();
|
|
||||||
randomAccessFile.close();
|
|
||||||
return true;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
new File(filePath).delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
119
app/src/main/java/eu/mhutti1/utils/storage/StorageDeviceUtils.kt
Normal file
119
app/src/main/java/eu/mhutti1/utils/storage/StorageDeviceUtils.kt
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Isaac Hutt <mhutti1@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 3 of the License, or
|
||||||
|
* 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
* MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package eu.mhutti1.utils.storage
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Environment
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileFilter
|
||||||
|
import java.io.RandomAccessFile
|
||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
|
object StorageDeviceUtils {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getStorageDevices(context: Context, writable: Boolean): List<StorageDevice> {
|
||||||
|
val storageDevices = ArrayList<StorageDevice>().apply {
|
||||||
|
add(environmentDevices(writable))
|
||||||
|
addAll(externalMountPointDevices())
|
||||||
|
addAll(externalFilesDirsDevices(context, writable))
|
||||||
|
}
|
||||||
|
return validate(storageDevices, writable)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun externalFilesDirsDevices(
|
||||||
|
context: Context,
|
||||||
|
writable: Boolean
|
||||||
|
) = ContextCompat.getExternalFilesDirs(context, "")
|
||||||
|
.filterNotNull()
|
||||||
|
.map { dir -> StorageDevice(generalisePath(dir.path, writable), false) }
|
||||||
|
|
||||||
|
private fun externalMountPointDevices(): Collection<StorageDevice> =
|
||||||
|
ExternalPaths.possiblePaths.fold(mutableListOf(), { acc, path ->
|
||||||
|
acc.apply {
|
||||||
|
if (path.endsWith("*")) {
|
||||||
|
addAll(devicesBeneath(File(path.substringBeforeLast("*"))))
|
||||||
|
} else {
|
||||||
|
add(StorageDevice(path, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
private fun devicesBeneath(directory: File) =
|
||||||
|
directory.listFiles(FileFilter(File::isDirectory))
|
||||||
|
?.map { dir -> StorageDevice(dir, false) }
|
||||||
|
.orEmpty()
|
||||||
|
|
||||||
|
private fun environmentDevices(
|
||||||
|
writable: Boolean
|
||||||
|
) =
|
||||||
|
StorageDevice(
|
||||||
|
generalisePath(Environment.getExternalStorageDirectory().path, writable),
|
||||||
|
Environment.isExternalStorageEmulated()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Remove app specific path from directories so that we can search them from the top
|
||||||
|
private fun generalisePath(path: String, writable: Boolean) =
|
||||||
|
if (writable) path
|
||||||
|
else path.substringBefore("/Android/data/")
|
||||||
|
|
||||||
|
// Amazingly file.canWrite() does not always return the correct value
|
||||||
|
private fun canWrite(file: File): Boolean = "$file/test.txt".let {
|
||||||
|
try {
|
||||||
|
RandomAccessFile(it, "rw").use { randomAccessFile ->
|
||||||
|
randomAccessFile.channel.use { channel ->
|
||||||
|
channel.lock().use { fileLock ->
|
||||||
|
fileLock.release()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ignore: Exception) {
|
||||||
|
false
|
||||||
|
} finally {
|
||||||
|
File(it).delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validate(
|
||||||
|
storageDevices: ArrayList<StorageDevice>,
|
||||||
|
writable: Boolean
|
||||||
|
) = storageDevices.asSequence().distinct()
|
||||||
|
.filter { it.file.exists() }
|
||||||
|
.filter { it.file.isDirectory }
|
||||||
|
.filter { canWrite(it.file) || !writable }
|
||||||
|
.filterNot(StorageDevice::isDuplicate)
|
||||||
|
.toList()
|
||||||
|
.also(StorageDeviceUtils::deleteStorageMarkers)
|
||||||
|
|
||||||
|
private fun deleteStorageMarkers(validatedDevices: List<StorageDevice>) {
|
||||||
|
validatedDevices.forEach { recursiveDeleteStorageMarkers(it.file) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun recursiveDeleteStorageMarkers(file: File) {
|
||||||
|
file.listFiles().forEach {
|
||||||
|
when {
|
||||||
|
it.isDirectory -> recursiveDeleteStorageMarkers(it)
|
||||||
|
it.extension == LOCATION_EXTENSION -> it.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,72 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Isaac Hutt <mhutti1@gmail.com>
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 3 of the License, or
|
|
||||||
* 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 General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
||||||
* MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package eu.mhutti1.utils.storage;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import org.kiwix.kiwixmobile.R;
|
|
||||||
|
|
||||||
class StorageSelectArrayAdapter extends ArrayAdapter<StorageDevice> {
|
|
||||||
|
|
||||||
private final String mInternal;
|
|
||||||
|
|
||||||
private final String mExternal;
|
|
||||||
|
|
||||||
public StorageSelectArrayAdapter(Context context, int resource, ArrayList<StorageDevice> devices,
|
|
||||||
String internal, String external) {
|
|
||||||
super(context, resource, devices);
|
|
||||||
mInternal = internal;
|
|
||||||
mExternal = external;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n") @Override
|
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
|
||||||
|
|
||||||
ViewHolder holder;
|
|
||||||
if (convertView == null) {
|
|
||||||
convertView = View.inflate(getContext(), R.layout.device_item, null);
|
|
||||||
holder = new ViewHolder();
|
|
||||||
holder.fileName = convertView.findViewById(R.id.file_name);
|
|
||||||
holder.fileSize = convertView.findViewById(R.id.file_size);
|
|
||||||
convertView.setTag(holder);
|
|
||||||
} else {
|
|
||||||
holder = (ViewHolder) convertView.getTag();
|
|
||||||
}
|
|
||||||
StorageDevice device = getItem(position);
|
|
||||||
if (device.isInternal()) {
|
|
||||||
holder.fileName.setText(mInternal);
|
|
||||||
} else {
|
|
||||||
holder.fileName.setText(mExternal);
|
|
||||||
}
|
|
||||||
holder.fileSize.setText(device.getSize() + " / " + device.getTotalSize());
|
|
||||||
|
|
||||||
return convertView;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder {
|
|
||||||
TextView fileName;
|
|
||||||
TextView fileSize;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Isaac Hutt <mhutti1@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 3 of the License, or
|
||||||
|
* 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
* MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package eu.mhutti1.utils.storage
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import kotlinx.android.extensions.LayoutContainer
|
||||||
|
import kotlinx.android.synthetic.main.device_item.file_name
|
||||||
|
import kotlinx.android.synthetic.main.device_item.file_size
|
||||||
|
import org.kiwix.kiwixmobile.R
|
||||||
|
import org.kiwix.kiwixmobile.R.string
|
||||||
|
import org.kiwix.kiwixmobile.extensions.inflate
|
||||||
|
|
||||||
|
internal class StorageSelectArrayAdapter(
|
||||||
|
context: Context,
|
||||||
|
devices: List<StorageDevice>
|
||||||
|
) : ArrayAdapter<StorageDevice>(context, 0, devices) {
|
||||||
|
|
||||||
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
|
var convertView = convertView
|
||||||
|
|
||||||
|
val holder: ViewHolder
|
||||||
|
if (convertView == null) {
|
||||||
|
convertView = parent.inflate(R.layout.device_item, false)
|
||||||
|
holder = ViewHolder(convertView)
|
||||||
|
convertView.tag = holder
|
||||||
|
} else {
|
||||||
|
holder = convertView.tag as ViewHolder
|
||||||
|
}
|
||||||
|
holder.bind(getItem(position)!!)
|
||||||
|
|
||||||
|
return convertView
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
internal inner class ViewHolder(override val containerView: View) : LayoutContainer {
|
||||||
|
fun bind(device: StorageDevice) {
|
||||||
|
file_name.setText(if (device.isInternal) string.internal_storage else string.external_storage)
|
||||||
|
file_size.text = device.availableSpace + " / " + device.totalSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,114 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Isaac Hutt <mhutti1@gmail.com>
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 3 of the License, or
|
|
||||||
* 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 General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
||||||
* MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package eu.mhutti1.utils.storage;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import androidx.fragment.app.DialogFragment;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import java.io.File;
|
|
||||||
import org.kiwix.kiwixmobile.R;
|
|
||||||
|
|
||||||
public class StorageSelectDialog extends DialogFragment implements ListView.OnItemClickListener {
|
|
||||||
|
|
||||||
// Activities/Fragments can create instances of a StorageSelectDialog and bind a listener to get its result
|
|
||||||
|
|
||||||
public static final String STORAGE_DIALOG_THEME = "THEME";
|
|
||||||
|
|
||||||
public static final String STORAGE_DIALOG_INTERNAL = "INTERNAL";
|
|
||||||
|
|
||||||
public static final String STORAGE_DIALOG_EXTERNAL = "EXTERNAL";
|
|
||||||
|
|
||||||
private StorageSelectArrayAdapter mAdapter;
|
|
||||||
|
|
||||||
private OnSelectListener mOnSelectListener;
|
|
||||||
private String mTitle;
|
|
||||||
|
|
||||||
private String mInternal = "Internal";
|
|
||||||
|
|
||||||
private String mExternal = "External";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
if (getArguments() != null) {
|
|
||||||
// Set string values
|
|
||||||
mInternal = getArguments().getString(STORAGE_DIALOG_INTERNAL, mInternal);
|
|
||||||
mExternal = getArguments().getString(STORAGE_DIALOG_EXTERNAL, mExternal);
|
|
||||||
// Set the theme to a supplied value
|
|
||||||
if (getArguments().containsKey(STORAGE_DIALOG_THEME)) {
|
|
||||||
setStyle(DialogFragment.STYLE_NORMAL, getArguments().getInt(STORAGE_DIALOG_THEME));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
|
||||||
Bundle savedInstanceState) {
|
|
||||||
View rootView = inflater.inflate(R.layout.storage_select_dialog, container, false);
|
|
||||||
TextView title = rootView.findViewById(R.id.title);
|
|
||||||
title.setText(mTitle);
|
|
||||||
ListView listView = rootView.findViewById(R.id.device_list);
|
|
||||||
mAdapter = new StorageSelectArrayAdapter(getActivity(), 0,
|
|
||||||
StorageDeviceUtils.getStorageDevices(getActivity(), true), mInternal, mExternal);
|
|
||||||
listView.setAdapter(mAdapter);
|
|
||||||
listView.setOnItemClickListener(this);
|
|
||||||
Button button = rootView.findViewById(R.id.button);
|
|
||||||
final EditText editText = rootView.findViewById(R.id.editText);
|
|
||||||
button.setOnClickListener(view -> {
|
|
||||||
if (editText.getText().length() != 0) {
|
|
||||||
String path = editText.getText().toString();
|
|
||||||
if (new File(path).exists()) {
|
|
||||||
mAdapter.add(new StorageDevice(path, false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return rootView;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
|
||||||
if (mOnSelectListener != null) {
|
|
||||||
mOnSelectListener.selectionCallback(mAdapter.getItem(position));
|
|
||||||
}
|
|
||||||
dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnSelectListener(OnSelectListener selectListener) {
|
|
||||||
mOnSelectListener = selectListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnSelectListener {
|
|
||||||
void selectionCallback(StorageDevice s);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void show(FragmentManager fm, String text) {
|
|
||||||
mTitle = text;
|
|
||||||
super.show(fm, text);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Isaac Hutt <mhutti1@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 3 of the License, or
|
||||||
|
* 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
* MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package eu.mhutti1.utils.storage
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.AdapterView.OnItemClickListener
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import kotlinx.android.synthetic.main.storage_select_dialog.device_list
|
||||||
|
import kotlinx.android.synthetic.main.storage_select_dialog.title
|
||||||
|
import org.kiwix.kiwixmobile.R
|
||||||
|
import org.kiwix.kiwixmobile.utils.StyleUtils
|
||||||
|
|
||||||
|
class StorageSelectDialog : DialogFragment() {
|
||||||
|
|
||||||
|
private var onSelectAction: ((StorageDevice) -> Unit)? = null
|
||||||
|
private var mAdapter: StorageSelectArrayAdapter? = null
|
||||||
|
|
||||||
|
private var mTitle: String? = null
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
setStyle(STYLE_NORMAL, StyleUtils.dialogStyle())
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View = inflater.inflate(R.layout.storage_select_dialog, container, false)
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
title.text = mTitle
|
||||||
|
mAdapter = StorageSelectArrayAdapter(
|
||||||
|
activity!!,
|
||||||
|
StorageDeviceUtils.getStorageDevices(activity!!, true)
|
||||||
|
)
|
||||||
|
device_list.adapter = mAdapter
|
||||||
|
device_list.onItemClickListener = OnItemClickListener { _, _, position, _ ->
|
||||||
|
onSelectAction?.invoke(mAdapter!!.getItem(position)!!)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun show(fm: FragmentManager, text: String) {
|
||||||
|
mTitle = text
|
||||||
|
super.show(fm, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnSelectListener(onSelectAction: (StorageDevice) -> Unit) {
|
||||||
|
this.onSelectAction = onSelectAction
|
||||||
|
}
|
||||||
|
}
|
@ -30,6 +30,8 @@ import javax.inject.Inject
|
|||||||
class NewBookDao @Inject constructor(private val box: Box<BookOnDiskEntity>) {
|
class NewBookDao @Inject constructor(private val box: Box<BookOnDiskEntity>) {
|
||||||
|
|
||||||
fun books() = box.asFlowable()
|
fun books() = box.asFlowable()
|
||||||
|
.doOnNext(::removeBooksThatDoNotExist)
|
||||||
|
.map { books -> books.filter { it.file.exists() } }
|
||||||
.map { it.map(::BookOnDisk) }
|
.map { it.map(::BookOnDisk) }
|
||||||
|
|
||||||
fun getBooks() = box.all.map(::BookOnDisk)
|
fun getBooks() = box.all.map(::BookOnDisk)
|
||||||
@ -52,4 +54,12 @@ class NewBookDao @Inject constructor(private val box: Box<BookOnDiskEntity>) {
|
|||||||
fun migrationInsert(books: ArrayList<Book>) {
|
fun migrationInsert(books: ArrayList<Book>) {
|
||||||
insert(books.map { BookOnDisk(book = it, file = it.file) })
|
insert(books.map { BookOnDisk(book = it, file = it.file) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun removeBooksThatDoNotExist(books: MutableList<BookOnDiskEntity>) {
|
||||||
|
delete(books.filterNot { it.file.exists() })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun delete(books: List<BookOnDiskEntity>) {
|
||||||
|
box.remove(books)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,15 +26,13 @@ import org.kiwix.kiwixmobile.database.newdb.entities.BookmarkEntity_
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewBookmarksDao @Inject constructor(val box: Box<BookmarkEntity>) {
|
class NewBookmarksDao @Inject constructor(val box: Box<BookmarkEntity>) {
|
||||||
fun getBookmarks(fromCurrentBook: Boolean): List<BookmarkItem> {
|
fun getBookmarks(fromCurrentBook: Boolean) = box.query {
|
||||||
return box.query {
|
if (fromCurrentBook) {
|
||||||
if (fromCurrentBook) {
|
equal(BookmarkEntity_.zimName, ZimContentProvider.getName() ?: "")
|
||||||
equal(BookmarkEntity_.zimId, ZimContentProvider.getId() ?: "")
|
}
|
||||||
}
|
order(BookmarkEntity_.bookmarkTitle)
|
||||||
order(BookmarkEntity_.bookmarkTitle)
|
}.find()
|
||||||
}.find()
|
.map(::BookmarkItem)
|
||||||
.map(::BookmarkItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getCurrentZimBookmarksUrl() = box.query {
|
fun getCurrentZimBookmarksUrl() = box.query {
|
||||||
equal(BookmarkEntity_.zimId, ZimContentProvider.getId() ?: "")
|
equal(BookmarkEntity_.zimId, ZimContentProvider.getId() ?: "")
|
||||||
|
@ -43,6 +43,7 @@ import java.io.File;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import kotlin.Unit;
|
||||||
import org.kiwix.kiwixmobile.BuildConfig;
|
import org.kiwix.kiwixmobile.BuildConfig;
|
||||||
import org.kiwix.kiwixmobile.KiwixApplication;
|
import org.kiwix.kiwixmobile.KiwixApplication;
|
||||||
import org.kiwix.kiwixmobile.R;
|
import org.kiwix.kiwixmobile.R;
|
||||||
@ -50,7 +51,6 @@ import org.kiwix.kiwixmobile.base.BaseActivity;
|
|||||||
import org.kiwix.kiwixmobile.main.MainActivity;
|
import org.kiwix.kiwixmobile.main.MainActivity;
|
||||||
import org.kiwix.kiwixmobile.utils.LanguageUtils;
|
import org.kiwix.kiwixmobile.utils.LanguageUtils;
|
||||||
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil;
|
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil;
|
||||||
import org.kiwix.kiwixmobile.utils.StyleUtils;
|
|
||||||
import org.kiwix.kiwixmobile.zim_manager.library_view.LibraryUtils;
|
import org.kiwix.kiwixmobile.zim_manager.library_view.LibraryUtils;
|
||||||
|
|
||||||
import static org.kiwix.kiwixmobile.utils.Constants.EXTRA_WEBVIEWS_LIST;
|
import static org.kiwix.kiwixmobile.utils.Constants.EXTRA_WEBVIEWS_LIST;
|
||||||
@ -81,9 +81,9 @@ public class KiwixSettingsActivity extends BaseActivity {
|
|||||||
allHistoryCleared = false;
|
allHistoryCleared = false;
|
||||||
|
|
||||||
getFragmentManager()
|
getFragmentManager()
|
||||||
.beginTransaction().
|
.beginTransaction().
|
||||||
replace(R.id.content_frame, new PrefsFragment())
|
replace(R.id.content_frame, new PrefsFragment())
|
||||||
.commit();
|
.commit();
|
||||||
|
|
||||||
setUpToolbar();
|
setUpToolbar();
|
||||||
}
|
}
|
||||||
@ -110,8 +110,8 @@ public class KiwixSettingsActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class PrefsFragment extends PreferenceFragment implements
|
public static class PrefsFragment extends PreferenceFragment implements
|
||||||
SettingsContract.View,
|
SettingsContract.View,
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener, StorageSelectDialog.OnSelectListener {
|
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
SettingsPresenter presenter;
|
SettingsPresenter presenter;
|
||||||
@ -329,29 +329,25 @@ public class KiwixSettingsActivity extends BaseActivity {
|
|||||||
|
|
||||||
public void openFolderSelect() {
|
public void openFolderSelect() {
|
||||||
StorageSelectDialog dialogFragment = new StorageSelectDialog();
|
StorageSelectDialog dialogFragment = new StorageSelectDialog();
|
||||||
Bundle b = new Bundle();
|
dialogFragment.setOnSelectListener(storageDevice -> {
|
||||||
b.putString(StorageSelectDialog.STORAGE_DIALOG_INTERNAL,
|
selectionCallback(storageDevice);
|
||||||
getResources().getString(R.string.internal_storage));
|
return Unit.INSTANCE;
|
||||||
b.putString(StorageSelectDialog.STORAGE_DIALOG_EXTERNAL,
|
});
|
||||||
getResources().getString(R.string.external_storage));
|
dialogFragment.show(((AppCompatActivity) getActivity()).getSupportFragmentManager(),
|
||||||
b.putInt(StorageSelectDialog.STORAGE_DIALOG_THEME, StyleUtils.dialogStyle());
|
getResources().getString(R.string.pref_storage));
|
||||||
dialogFragment.setArguments(b);
|
|
||||||
dialogFragment.setOnSelectListener(this);
|
|
||||||
dialogFragment.show(((AppCompatActivity) getActivity()).getSupportFragmentManager(), getResources().getString(R.string.pref_storage));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void selectionCallback(StorageDevice storageDevice) {
|
||||||
public void selectionCallback(StorageDevice storageDevice) {
|
findPreference(PREF_STORAGE).setSummary(storageDevice.getAvailableSpace());
|
||||||
findPreference(PREF_STORAGE).setSummary(storageDevice.getSize());
|
|
||||||
sharedPreferenceUtil.putPrefStorage(storageDevice.getName());
|
sharedPreferenceUtil.putPrefStorage(storageDevice.getName());
|
||||||
if (storageDevice.isInternal()) {
|
if (storageDevice.isInternal()) {
|
||||||
findPreference(PREF_STORAGE).setTitle(getResources().getString(R.string.internal_storage));
|
findPreference(PREF_STORAGE).setTitle(getResources().getString(R.string.internal_storage));
|
||||||
sharedPreferenceUtil.putPrefStorageTitle(
|
sharedPreferenceUtil.putPrefStorageTitle(
|
||||||
getResources().getString(R.string.internal_storage));
|
getResources().getString(R.string.internal_storage));
|
||||||
} else {
|
} else {
|
||||||
findPreference(PREF_STORAGE).setTitle(getResources().getString(R.string.external_storage));
|
findPreference(PREF_STORAGE).setTitle(getResources().getString(R.string.external_storage));
|
||||||
sharedPreferenceUtil.putPrefStorageTitle(
|
sharedPreferenceUtil.putPrefStorageTitle(
|
||||||
getResources().getString(R.string.external_storage));
|
getResources().getString(R.string.external_storage));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.kiwix.kiwixmobile.zim_manager.library_view
|
package org.kiwix.kiwixmobile.zim_manager.library_view
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -49,7 +48,6 @@ import org.kiwix.kiwixmobile.utils.DialogShower
|
|||||||
import org.kiwix.kiwixmobile.utils.KiwixDialog.YesNoDialog.WifiOnly
|
import org.kiwix.kiwixmobile.utils.KiwixDialog.YesNoDialog.WifiOnly
|
||||||
import org.kiwix.kiwixmobile.utils.NetworkUtils
|
import org.kiwix.kiwixmobile.utils.NetworkUtils
|
||||||
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil
|
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil
|
||||||
import org.kiwix.kiwixmobile.utils.StyleUtils
|
|
||||||
import org.kiwix.kiwixmobile.utils.TestingUtils
|
import org.kiwix.kiwixmobile.utils.TestingUtils
|
||||||
import org.kiwix.kiwixmobile.zim_manager.NetworkState
|
import org.kiwix.kiwixmobile.zim_manager.NetworkState
|
||||||
import org.kiwix.kiwixmobile.zim_manager.NetworkState.CONNECTED
|
import org.kiwix.kiwixmobile.zim_manager.NetworkState.CONNECTED
|
||||||
@ -210,23 +208,9 @@ class LibraryFragment : BaseFragment() {
|
|||||||
private fun notEnoughSpaceAvailable(item: BookItem) =
|
private fun notEnoughSpaceAvailable(item: BookItem) =
|
||||||
spaceAvailable < item.book.size.toLong() * 1024f
|
spaceAvailable < item.book.size.toLong() * 1024f
|
||||||
|
|
||||||
@SuppressLint("ImplicitSamInstance")
|
private fun showStorageSelectDialog() = StorageSelectDialog()
|
||||||
private fun showStorageSelectDialog() {
|
.apply {
|
||||||
StorageSelectDialog()
|
setOnSelectListener(::storeDeviceInPreferences)
|
||||||
.apply {
|
}
|
||||||
arguments = Bundle().apply {
|
.show(fragmentManager!!, getString(string.pref_storage))
|
||||||
putString(
|
|
||||||
StorageSelectDialog.STORAGE_DIALOG_INTERNAL,
|
|
||||||
this@LibraryFragment.getString(string.internal_storage)
|
|
||||||
)
|
|
||||||
putString(
|
|
||||||
StorageSelectDialog.STORAGE_DIALOG_EXTERNAL,
|
|
||||||
this@LibraryFragment.getString(string.external_storage)
|
|
||||||
)
|
|
||||||
putInt(StorageSelectDialog.STORAGE_DIALOG_THEME, StyleUtils.dialogStyle())
|
|
||||||
}
|
|
||||||
setOnSelectListener(::storeDeviceInPreferences)
|
|
||||||
}
|
|
||||||
.show(fragmentManager, getString(string.pref_storage))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,53 +1,23 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:orientation="vertical"
|
||||||
android:orientation="vertical"
|
android:padding="10dp"
|
||||||
android:padding="10dp"
|
>
|
||||||
>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/title"
|
android:id="@+id/title"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
android:textSize="22sp"
|
android:textSize="22sp"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ListView
|
<ListView
|
||||||
android:id="@+id/device_list"
|
android:id="@+id/device_list"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
>
|
/>
|
||||||
|
|
||||||
|
|
||||||
</ListView>
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
>
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/editText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:ems="10"
|
|
||||||
android:inputType="textPersonName"
|
|
||||||
android:text="@string/slash"
|
|
||||||
android:importantForAutofill="no"
|
|
||||||
tools:targetApi="o"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
android:id="@+id/button"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="@string/plus"
|
|
||||||
/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -6,6 +6,4 @@
|
|||||||
<item>large</item>
|
<item>large</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string name="slash">/</string>
|
|
||||||
<string name="plus">+</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -29,7 +29,7 @@ class LanguageTest {
|
|||||||
@Nested
|
@Nested
|
||||||
inner class Equals {
|
inner class Equals {
|
||||||
@Test
|
@Test
|
||||||
@Suppress("UnusedEquals", "ReplaceCallWithBinaryOperator") //cannot == Unit
|
@Suppress("UnusedEquals", "ReplaceCallWithBinaryOperator") // cannot == Unit
|
||||||
fun `throws exception when object is not language item`() {
|
fun `throws exception when object is not language item`() {
|
||||||
assertThrows(ClassCastException::class.java) { language().equals(Unit) }
|
assertThrows(ClassCastException::class.java) { language().equals(Unit) }
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user