mirror of
https://github.com/ultrasonic/ultrasonic
synced 2025-02-05 19:47:33 +01:00
Merge changes
This commit is contained in:
commit
3f570636dd
@ -7,7 +7,7 @@ ext.versions = [
|
||||
|
||||
navigation : "2.3.5",
|
||||
gradlePlugin : "4.2.2",
|
||||
androidxcore : "1.5.0",
|
||||
androidxcore : "1.6.0",
|
||||
ktlint : "0.37.1",
|
||||
ktlintGradle : "10.2.0",
|
||||
detekt : "1.18.1",
|
||||
@ -23,7 +23,7 @@ ext.versions = [
|
||||
room : "2.3.0",
|
||||
kotlin : "1.5.31",
|
||||
kotlinxCoroutines : "1.5.2-native-mt",
|
||||
viewModelKtx : "2.2.0",
|
||||
viewModelKtx : "2.3.0",
|
||||
|
||||
retrofit : "2.6.4",
|
||||
jackson : "2.9.5",
|
||||
@ -35,7 +35,7 @@ ext.versions = [
|
||||
junit4 : "4.13.2",
|
||||
junit5 : "5.8.1",
|
||||
mockito : "4.0.0",
|
||||
mockitoKotlin : "3.2.0",
|
||||
mockitoKotlin : "4.0.0",
|
||||
kluent : "1.68",
|
||||
apacheCodecs : "1.15",
|
||||
robolectric : "4.6.1",
|
||||
|
@ -1,6 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<issues format="5" by="lint 4.2.2" client="gradle" variant="release" version="4.2.2">
|
||||
|
||||
<issue
|
||||
id="ObsoleteLintCustomCheck"
|
||||
message="Lint found an issue registry (`androidx.appcompat.AppCompatIssueRegistry`) which is older than the current API level; these checks may not work correctly.

Recompile the checks against the latest version. Custom check API version is 7 (4.0), current lint API level is 8 (4.1)">
|
||||
<location
|
||||
file="../../../../.gradle/caches/transforms-3/2f10f1fe0ff7ab74304d702879de0789/transformed/appcompat-1.2.0/jars/lint.jar"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteLintCustomCheck"
|
||||
message="Lint found an issue registry (`timber.lint.TimberIssueRegistry`) which is older than the current API level; these checks may not work correctly.

Recompile the checks against the latest version. Custom check API version is 1 (3.1), current lint API level is 8 (4.1)">
|
||||
<location
|
||||
file="../../../../.gradle/caches/transforms-3/e9d816753daf5450613abd98ccf3b80c/transformed/jetified-timber-4.7.1/jars/lint.jar"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="IncludeLayoutParam"
|
||||
message="Layout parameter `layout_gravity` ignored unless both `layout_width` and `layout_height` are also specified on `<include>` tag"
|
||||
@ -188,39 +202,6 @@
|
||||
column="5"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnusedResources"
|
||||
message="The resource `R.drawable.btn_bg` appears to be unused"
|
||||
errorLine1=" <item android:drawable="@color/ics_opaque" android:state_pressed="true"/>"
|
||||
errorLine2="^">
|
||||
<location
|
||||
file="src/main/res/drawable/btn_bg.xml"
|
||||
line="14"
|
||||
column="1"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnusedResources"
|
||||
message="The resource `R.color.ics_opaque` appears to be unused"
|
||||
errorLine1=" <color name="ics_opaque">#8033b5e5</color>"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/values/colors.xml"
|
||||
line="9"
|
||||
column="12"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnusedResources"
|
||||
message="The resource `R.color.md__transparent` appears to be unused"
|
||||
errorLine1=" <color name="md__transparent">#00000000</color>"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/values/colors.xml"
|
||||
line="10"
|
||||
column="12"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnusedResources"
|
||||
message="The resource `R.drawable.ic_menu_arrow` appears to be unused"
|
||||
@ -232,39 +213,6 @@
|
||||
column="1"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnusedResources"
|
||||
message="The resource `R.drawable.line` appears to be unused"
|
||||
errorLine1="<shape xmlns:android="http://schemas.android.com/apk/res/android""
|
||||
errorLine2="^">
|
||||
<location
|
||||
file="src/main/res/drawable/line.xml"
|
||||
line="2"
|
||||
column="1"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnusedResources"
|
||||
message="The resource `R.drawable.line_drawable` appears to be unused"
|
||||
errorLine1="<selector xmlns:android="http://schemas.android.com/apk/res/android">"
|
||||
errorLine2="^">
|
||||
<location
|
||||
file="src/main/res/drawable/line_drawable.xml"
|
||||
line="2"
|
||||
column="1"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnusedResources"
|
||||
message="The resource `R.menu.main` appears to be unused"
|
||||
errorLine1="<menu"
|
||||
errorLine2="^">
|
||||
<location
|
||||
file="src/main/res/menu/main.xml"
|
||||
line="2"
|
||||
column="1"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnusedResources"
|
||||
message="The resource `R.drawable.menu_arrow` appears to be unused"
|
||||
@ -1730,28 +1678,6 @@
|
||||
column="10"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ContentDescription"
|
||||
message="Missing `contentDescription` attribute on image"
|
||||
errorLine1=" <ImageView"
|
||||
errorLine2=" ~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/layout/now_playing.xml"
|
||||
line="18"
|
||||
column="10"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ContentDescription"
|
||||
message="Missing `contentDescription` attribute on image"
|
||||
errorLine1=" <ImageView"
|
||||
errorLine2=" ~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/layout/now_playing.xml"
|
||||
line="55"
|
||||
column="10"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ContentDescription"
|
||||
message="Missing `contentDescription` attribute on image"
|
||||
|
@ -2,8 +2,8 @@ package org.moire.ultrasonic.data
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.room.Room
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -24,7 +24,7 @@ import timber.log.Timber
|
||||
*/
|
||||
class ActiveServerProvider(
|
||||
private val repository: ServerSettingDao
|
||||
) {
|
||||
) : CoroutineScope by CoroutineScope(Dispatchers.IO) {
|
||||
private var cachedServer: ServerSetting? = null
|
||||
private var cachedDatabase: MetaDatabase? = null
|
||||
private var cachedServerId: Int? = null
|
||||
@ -83,7 +83,7 @@ class ActiveServerProvider(
|
||||
return
|
||||
}
|
||||
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
launch {
|
||||
val serverId = repository.findByIndex(index)?.id ?: 0
|
||||
setActiveServerId(serverId)
|
||||
}
|
||||
@ -133,7 +133,7 @@ class ActiveServerProvider(
|
||||
* Sets the minimum Subsonic API version of the current server.
|
||||
*/
|
||||
fun setMinimumApiVersion(apiVersion: String) {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
launch {
|
||||
if (cachedServer != null) {
|
||||
cachedServer!!.minimumApiVersion = apiVersion
|
||||
repository.update(cachedServer!!)
|
||||
|
@ -55,6 +55,7 @@ import java.io.File
|
||||
/**
|
||||
* Shows main app settings.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
class SettingsFragment :
|
||||
PreferenceFragmentCompat(),
|
||||
OnSharedPreferenceChangeListener,
|
||||
|
@ -22,6 +22,7 @@ import org.moire.ultrasonic.domain.MusicDirectory
|
||||
import org.moire.ultrasonic.util.FileUtil
|
||||
import org.moire.ultrasonic.util.StorageFile
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import org.moire.ultrasonic.util.Util.safeClose
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
@ -187,10 +188,10 @@ class ImageLoader(
|
||||
outputStream = FileOutputStream(file)
|
||||
outputStream.write(bytes)
|
||||
} finally {
|
||||
Util.close(outputStream)
|
||||
outputStream.safeClose()
|
||||
}
|
||||
} finally {
|
||||
Util.close(inputStream)
|
||||
inputStream.safeClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import org.moire.ultrasonic.util.FileUtil
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import org.moire.ultrasonic.util.Util.safeClose
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
@ -38,7 +38,7 @@ class FileLoggerTree : Timber.DebugTree() {
|
||||
// Using base class DebugTree here, we don't want to try to log this into file
|
||||
super.log(6, TAG, String.format("Failed to write log to %s", file), x)
|
||||
} finally {
|
||||
if (writer != null) Util.close(writer)
|
||||
writer.safeClose()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ import org.moire.ultrasonic.util.StorageFile
|
||||
import org.moire.ultrasonic.util.FileUtil
|
||||
import org.moire.ultrasonic.util.Settings
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import org.moire.ultrasonic.util.Util.safeClose
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
@ -133,9 +134,9 @@ class DownloadFile(
|
||||
|
||||
fun delete() {
|
||||
cancelDownload()
|
||||
Util.delete(partialFile)
|
||||
Util.delete(completeFile)
|
||||
Util.delete(saveFile)
|
||||
FileUtil.delete(partialFile)
|
||||
FileUtil.delete(completeFile)
|
||||
FileUtil.delete(saveFile)
|
||||
|
||||
Util.scanMedia(saveFile)
|
||||
}
|
||||
@ -149,11 +150,11 @@ class DownloadFile(
|
||||
fun cleanup(): Boolean {
|
||||
var ok = true
|
||||
if (StorageFile.isPathExists(completeFile) || StorageFile.isPathExists(saveFile)) {
|
||||
ok = Util.delete(partialFile)
|
||||
ok = FileUtil.delete(partialFile)
|
||||
}
|
||||
|
||||
if (StorageFile.isPathExists(saveFile)) {
|
||||
ok = ok and Util.delete(completeFile)
|
||||
ok = ok and FileUtil.delete(completeFile)
|
||||
}
|
||||
|
||||
return ok
|
||||
@ -168,14 +169,14 @@ class DownloadFile(
|
||||
private fun doPendingRename() {
|
||||
try {
|
||||
if (saveWhenDone) {
|
||||
Util.renameFile(completeFile, saveFile)
|
||||
FileUtil.renameFile(completeFile, saveFile)
|
||||
saveWhenDone = false
|
||||
} else if (completeWhenDone) {
|
||||
if (save) {
|
||||
Util.renameFile(partialFile, saveFile)
|
||||
FileUtil.renameFile(partialFile, saveFile)
|
||||
Util.scanMedia(saveFile)
|
||||
} else {
|
||||
Util.renameFile(partialFile, completeFile)
|
||||
FileUtil.renameFile(partialFile, completeFile)
|
||||
}
|
||||
completeWhenDone = false
|
||||
}
|
||||
@ -207,7 +208,7 @@ class DownloadFile(
|
||||
if (isPlaying) {
|
||||
saveWhenDone = true
|
||||
} else {
|
||||
Util.renameFile(completeFile, saveFile)
|
||||
FileUtil.renameFile(completeFile, saveFile)
|
||||
}
|
||||
} else {
|
||||
Timber.i("%s already exists. Skipping.", completeFile)
|
||||
@ -276,16 +277,16 @@ class DownloadFile(
|
||||
completeWhenDone = true
|
||||
} else {
|
||||
if (save) {
|
||||
Util.renameFile(partialFile, saveFile)
|
||||
FileUtil.renameFile(partialFile, saveFile)
|
||||
Util.scanMedia(saveFile)
|
||||
} else {
|
||||
Util.renameFile(partialFile, completeFile)
|
||||
FileUtil.renameFile(partialFile, completeFile)
|
||||
}
|
||||
}
|
||||
} catch (all: Exception) {
|
||||
Util.close(outputStream)
|
||||
Util.delete(completeFile)
|
||||
Util.delete(saveFile)
|
||||
outputStream.safeClose()
|
||||
FileUtil.delete(completeFile)
|
||||
FileUtil.delete(saveFile)
|
||||
if (!isCancelled) {
|
||||
isFailed = true
|
||||
if (retryCount > 1) {
|
||||
@ -298,8 +299,8 @@ class DownloadFile(
|
||||
Timber.w(all, "Failed to download '%s'.", song)
|
||||
}
|
||||
} finally {
|
||||
Util.close(inputStream)
|
||||
Util.close(outputStream)
|
||||
inputStream.safeClose()
|
||||
outputStream.safeClose()
|
||||
CacheCleaner().cleanSpace()
|
||||
downloader.checkDownloads()
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ import org.moire.ultrasonic.domain.Share
|
||||
import org.moire.ultrasonic.domain.UserInfo
|
||||
import org.moire.ultrasonic.util.Constants
|
||||
import org.moire.ultrasonic.util.FileUtil
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import org.moire.ultrasonic.util.Util.safeClose
|
||||
import timber.log.Timber
|
||||
import java.io.FileReader
|
||||
import java.io.FileWriter
|
||||
@ -215,8 +215,8 @@ class OfflineMusicService : MusicService, KoinComponent {
|
||||
}
|
||||
playlist
|
||||
} finally {
|
||||
Util.close(buffer)
|
||||
Util.close(reader)
|
||||
buffer.safeClose()
|
||||
reader.safeClose()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.moire.ultrasonic.util
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.AsyncTask
|
||||
import android.os.StatFs
|
||||
import android.system.Os
|
||||
@ -16,7 +15,7 @@ import org.moire.ultrasonic.util.FileUtil.getPlaylistFile
|
||||
import org.moire.ultrasonic.util.FileUtil.listFiles
|
||||
import org.moire.ultrasonic.util.FileUtil.musicDirectory
|
||||
import org.moire.ultrasonic.util.Settings.cacheSizeMB
|
||||
import org.moire.ultrasonic.util.Util.delete
|
||||
import org.moire.ultrasonic.util.FileUtil.delete
|
||||
import org.moire.ultrasonic.util.Util.formatBytes
|
||||
import timber.log.Timber
|
||||
|
||||
@ -60,9 +59,12 @@ class CacheCleaner {
|
||||
Thread.currentThread().name = "BackgroundCleanup"
|
||||
val files: MutableList<StorageFile> = ArrayList()
|
||||
val dirs: MutableList<StorageFile> = ArrayList()
|
||||
|
||||
findCandidatesForDeletion(musicDirectory, files, dirs)
|
||||
|
||||
sortByAscendingModificationTime(files)
|
||||
val filesToNotDelete = findFilesToNotDelete()
|
||||
|
||||
deleteFiles(files, filesToNotDelete, getMinimumDelete(files), true)
|
||||
deleteEmptyDirs(dirs, filesToNotDelete)
|
||||
} catch (all: RuntimeException) {
|
||||
@ -76,10 +78,14 @@ class CacheCleaner {
|
||||
override fun doInBackground(vararg params: Void?): Void? {
|
||||
try {
|
||||
Thread.currentThread().name = "BackgroundSpaceCleanup"
|
||||
|
||||
val files: MutableList<StorageFile> = ArrayList()
|
||||
val dirs: MutableList<StorageFile> = ArrayList()
|
||||
|
||||
findCandidatesForDeletion(musicDirectory, files, dirs)
|
||||
|
||||
val bytesToDelete = getMinimumDelete(files)
|
||||
|
||||
if (bytesToDelete > 0L) {
|
||||
sortByAscendingModificationTime(files)
|
||||
val filesToNotDelete = findFilesToNotDelete()
|
||||
@ -99,12 +105,15 @@ class CacheCleaner {
|
||||
ActiveServerProvider::class.java
|
||||
)
|
||||
Thread.currentThread().name = "BackgroundPlaylistsCleanup"
|
||||
|
||||
val server = activeServerProvider.value.getActiveServer().name
|
||||
val playlistFiles = listFiles(getPlaylistDirectory(server))
|
||||
val playlists = params[0]
|
||||
|
||||
for ((_, name) in playlists) {
|
||||
playlistFiles.remove(getPlaylistFile(server, name))
|
||||
}
|
||||
|
||||
for (playlist in playlistFiles) {
|
||||
playlist.delete()
|
||||
}
|
||||
@ -119,9 +128,8 @@ class CacheCleaner {
|
||||
private const val MIN_FREE_SPACE = 500 * 1024L * 1024L
|
||||
private fun deleteEmptyDirs(dirs: Iterable<StorageFile>, doNotDelete: Collection<String>) {
|
||||
for (dir in dirs) {
|
||||
if (doNotDelete.contains(dir.getPath())) {
|
||||
continue
|
||||
}
|
||||
if (doNotDelete.contains(dir.getPath())) continue
|
||||
|
||||
var children = dir.listFiles()
|
||||
if (children != null) {
|
||||
// No songs left in the folder
|
||||
@ -140,11 +148,11 @@ class CacheCleaner {
|
||||
}
|
||||
|
||||
private fun getMinimumDelete(files: List<StorageFile>): Long {
|
||||
if (files.isEmpty()) {
|
||||
return 0L
|
||||
}
|
||||
if (files.isEmpty()) return 0L
|
||||
|
||||
val cacheSizeBytes = cacheSizeMB * 1024L * 1024L
|
||||
var bytesUsedBySubsonic = 0L
|
||||
|
||||
for (file in files) {
|
||||
bytesUsedBySubsonic += file.length()
|
||||
}
|
||||
@ -154,6 +162,7 @@ class CacheCleaner {
|
||||
val minFsAvailability: Long
|
||||
val bytesTotalFs: Long
|
||||
val bytesAvailableFs: Long
|
||||
|
||||
if (files[0].isRawFile()) {
|
||||
val stat = StatFs(files[0].getRawFilePath())
|
||||
bytesTotalFs = stat.blockCountLong * stat.blockSizeLong
|
||||
@ -169,6 +178,7 @@ class CacheCleaner {
|
||||
minFsAvailability = bytesTotalFs - MIN_FREE_SPACE
|
||||
descriptor.close()
|
||||
}
|
||||
|
||||
val bytesToDeleteCacheLimit = (bytesUsedBySubsonic - cacheSizeBytes).coerceAtLeast(0L)
|
||||
val bytesToDeleteFsLimit = (bytesUsedFs - minFsAvailability).coerceAtLeast(0L)
|
||||
val bytesToDelete = bytesToDeleteCacheLimit.coerceAtLeast(bytesToDeleteFsLimit)
|
||||
@ -181,6 +191,7 @@ class CacheCleaner {
|
||||
Timber.i("Cache limit : %s", formatBytes(cacheSizeBytes))
|
||||
Timber.i("Cache size before : %s", formatBytes(bytesUsedBySubsonic))
|
||||
Timber.i("Minimum to delete : %s", formatBytes(bytesToDelete))
|
||||
|
||||
return bytesToDelete
|
||||
}
|
||||
|
||||
@ -203,6 +214,7 @@ class CacheCleaner {
|
||||
return
|
||||
}
|
||||
var bytesDeleted = 0L
|
||||
|
||||
for (file in files) {
|
||||
if (!deletePartials && bytesDeleted > bytesToDelete) break
|
||||
if (bytesToDelete > bytesDeleted || deletePartials && isPartial(file)) {
|
||||
@ -244,10 +256,12 @@ class CacheCleaner {
|
||||
val downloader = inject<Downloader>(
|
||||
Downloader::class.java
|
||||
)
|
||||
|
||||
for (downloadFile in downloader.value.all) {
|
||||
filesToNotDelete.add(downloadFile.partialFile)
|
||||
filesToNotDelete.add(downloadFile.completeOrSaveFile)
|
||||
}
|
||||
|
||||
filesToNotDelete.add(musicDirectory.getPath())
|
||||
return filesToNotDelete
|
||||
}
|
||||
|
@ -26,9 +26,11 @@ import java.util.TreeSet
|
||||
import java.util.regex.Pattern
|
||||
import org.moire.ultrasonic.app.UApp
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
import org.moire.ultrasonic.util.Util.safeClose
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
object FileUtil {
|
||||
|
||||
private val FILE_SYSTEM_UNSAFE = arrayOf("/", "\\", "..", ":", "\"", "?", "*", "<", ">", "|")
|
||||
@ -423,7 +425,7 @@ object FileUtil {
|
||||
Timber.w("Failed to serialize object to %s", file)
|
||||
false
|
||||
} finally {
|
||||
Util.close(out)
|
||||
out.safeClose()
|
||||
}
|
||||
}
|
||||
|
||||
@ -445,7 +447,7 @@ object FileUtil {
|
||||
Timber.w(all, "Failed to deserialize object from %s", file)
|
||||
null
|
||||
} finally {
|
||||
Util.close(inStream)
|
||||
inStream.safeClose()
|
||||
}
|
||||
}
|
||||
|
||||
@ -473,8 +475,39 @@ object FileUtil {
|
||||
Timber.w("Failed to save playlist: %s", name)
|
||||
throw e
|
||||
} finally {
|
||||
bw.close()
|
||||
fw.close()
|
||||
bw.safeClose()
|
||||
fw.safeClose()
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun renameFile(from: String, to: String) {
|
||||
StorageFile.rename(from, to)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun delete(file: File?): Boolean {
|
||||
if (file != null && file.exists()) {
|
||||
if (!file.delete()) {
|
||||
Timber.w("Failed to delete file %s", file)
|
||||
return false
|
||||
}
|
||||
Timber.i("Deleted file %s", file)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun delete(file: String?): Boolean {
|
||||
if (file != null && StorageFile.isPathExists(file)) {
|
||||
if (!StorageFile.getFromPath(file).delete()) {
|
||||
Timber.w("Failed to delete file %s", file)
|
||||
return false
|
||||
}
|
||||
Timber.i("Deleted file %s", file)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ package org.moire.ultrasonic.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.ConnectivityManager
|
||||
import android.os.Build
|
||||
import androidx.preference.PreferenceManager
|
||||
import java.util.regex.Pattern
|
||||
@ -70,17 +69,21 @@ object Settings {
|
||||
@JvmStatic
|
||||
val maxBitRate: Int
|
||||
get() {
|
||||
val manager = Util.getConnectivityManager()
|
||||
val networkInfo = manager.activeNetworkInfo ?: return 0
|
||||
val wifi = networkInfo.type == ConnectivityManager.TYPE_WIFI
|
||||
val preferences = preferences
|
||||
return preferences.getString(
|
||||
if (wifi) Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI
|
||||
else Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE,
|
||||
"0"
|
||||
)!!.toInt()
|
||||
val network = Util.networkInfo()
|
||||
|
||||
if (!network.connected) return 0
|
||||
|
||||
if (network.unmetered) {
|
||||
return maxWifiBitRate
|
||||
} else {
|
||||
return maxMobileBitRate
|
||||
}
|
||||
}
|
||||
|
||||
private var maxWifiBitRate by StringIntSetting(Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI)
|
||||
|
||||
private var maxMobileBitRate by StringIntSetting(Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE)
|
||||
|
||||
@JvmStatic
|
||||
val preloadCount: Int
|
||||
get() {
|
||||
|
@ -3,6 +3,7 @@ package org.moire.ultrasonic.util
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import java.io.PrintWriter
|
||||
import org.moire.ultrasonic.util.Util.safeClose
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
@ -34,7 +35,7 @@ class SubsonicUncaughtExceptionHandler(
|
||||
} catch (x: Throwable) {
|
||||
Timber.e(x, "Failed to write stack trace to %s", file)
|
||||
} finally {
|
||||
Util.close(printWriter)
|
||||
printWriter.safeClose()
|
||||
defaultHandler?.uncaughtException(thread, throwable)
|
||||
}
|
||||
}
|
||||
|
@ -23,9 +23,13 @@ import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.media.MediaScannerConnection
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
|
||||
import android.net.Uri
|
||||
import android.net.wifi.WifiManager
|
||||
import android.net.wifi.WifiManager.WifiLock
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.Parcelable
|
||||
@ -40,6 +44,7 @@ import androidx.annotation.AnyRes
|
||||
import androidx.media.utils.MediaConstants
|
||||
import java.io.Closeable
|
||||
import java.io.IOException
|
||||
import java.io.File
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.security.MessageDigest
|
||||
import java.text.DecimalFormat
|
||||
@ -57,7 +62,6 @@ import org.moire.ultrasonic.domain.PlayerState
|
||||
import org.moire.ultrasonic.domain.SearchResult
|
||||
import org.moire.ultrasonic.service.DownloadFile
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
private const val LINE_LENGTH = 60
|
||||
private const val DEGRADE_PRECISION_AFTER = 10
|
||||
@ -98,7 +102,7 @@ object Util {
|
||||
when (Settings.theme.lowercase()) {
|
||||
Constants.PREFERENCES_KEY_THEME_DARK,
|
||||
"fullscreen" -> {
|
||||
context!!.setTheme(R.style.UltrasonicTheme)
|
||||
context!!.setTheme(R.style.UltrasonicTheme_Dark)
|
||||
}
|
||||
Constants.PREFERENCES_KEY_THEME_BLACK -> {
|
||||
context!!.setTheme(R.style.UltrasonicTheme_Black)
|
||||
@ -110,44 +114,6 @@ object Util {
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun renameFile(from: String, to: String) {
|
||||
StorageFile.rename(from, to)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun close(closeable: Closeable?) {
|
||||
try {
|
||||
closeable?.close()
|
||||
} catch (_: Throwable) {
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun delete(file: String?): Boolean {
|
||||
if (file != null && StorageFile.isPathExists(file)) {
|
||||
if (!StorageFile.getFromPath(file).delete()) {
|
||||
Timber.w("Failed to delete file %s", file)
|
||||
return false
|
||||
}
|
||||
Timber.i("Deleted file %s", file)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun delete(file: File?): Boolean {
|
||||
if (file != null && file.exists()) {
|
||||
if (!file.delete()) {
|
||||
Timber.w("Failed to delete file %s", file)
|
||||
return false
|
||||
}
|
||||
Timber.i("Deleted file %s", file)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun toast(context: Context?, messageId: Int, shortDuration: Boolean = true) {
|
||||
@ -356,14 +322,45 @@ object Util {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a usable network for downloading media is available
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
@JvmStatic
|
||||
fun isNetworkConnected(): Boolean {
|
||||
val manager = getConnectivityManager()
|
||||
val networkInfo = manager.activeNetworkInfo
|
||||
val connected = networkInfo != null && networkInfo.isConnected
|
||||
val wifiConnected = connected && networkInfo!!.type == ConnectivityManager.TYPE_WIFI
|
||||
val info = networkInfo()
|
||||
val isUnmetered = info.unmetered
|
||||
val wifiRequired = Settings.isWifiRequiredForDownload
|
||||
return connected && (!wifiRequired || wifiConnected)
|
||||
return info.connected && (!wifiRequired || isUnmetered)
|
||||
}
|
||||
|
||||
/**
|
||||
* Query connectivity status
|
||||
*
|
||||
* @return NetworkInfo object
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
fun networkInfo(): NetworkInfo {
|
||||
val manager = getConnectivityManager()
|
||||
val info = NetworkInfo()
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val network: Network? = manager.activeNetwork
|
||||
val capabilities = manager.getNetworkCapabilities(network)
|
||||
|
||||
if (capabilities != null) {
|
||||
info.unmetered = capabilities.hasCapability(NET_CAPABILITY_NOT_METERED)
|
||||
info.connected = capabilities.hasCapability(NET_CAPABILITY_INTERNET)
|
||||
}
|
||||
} else {
|
||||
val networkInfo = manager.activeNetworkInfo
|
||||
if (networkInfo != null) {
|
||||
info.unmetered = networkInfo.type == ConnectivityManager.TYPE_WIFI
|
||||
info.connected = networkInfo.isConnected
|
||||
}
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@ -904,4 +901,23 @@ object Util {
|
||||
val context = appContext()
|
||||
return context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Small data class to store information about the current network
|
||||
**/
|
||||
data class NetworkInfo(
|
||||
var connected: Boolean = false,
|
||||
var unmetered: Boolean = false
|
||||
)
|
||||
|
||||
/**
|
||||
* Closes a Closeable while ignoring any errors.
|
||||
**/
|
||||
fun Closeable?.safeClose() {
|
||||
try {
|
||||
this?.close()
|
||||
} catch (_: Exception) {
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2010 The Android Open Source Project Licensed under the
|
||||
Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
|
||||
OR CONDITIONS OF ANY KIND, either express or implied. See the License for
|
||||
the specific language governing permissions and limitations under the License.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android" android:exitFadeDuration="@android:integer/config_mediumAnimTime">
|
||||
|
||||
<item android:drawable="@color/ics_opaque" android:state_pressed="true"/>
|
||||
<item android:drawable="@color/ics_opaque" android:state_enabled="true" android:state_focused="true"/>
|
||||
|
||||
</selector>
|
@ -4,8 +4,8 @@
|
||||
|
||||
<gradient
|
||||
android:angle="90"
|
||||
android:endColor="#00000000"
|
||||
android:startColor="#80000000"
|
||||
android:endColor="@android:color/transparent"
|
||||
android:startColor="#73111111"
|
||||
android:type="linear" />
|
||||
|
||||
</shape>
|
@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<solid android:color="@android:color/darker_gray" />
|
||||
|
||||
<padding
|
||||
android:top="10dp"
|
||||
android:left="10dp"
|
||||
android:right="10dp"
|
||||
android:bottom="10dp"/>
|
||||
</shape>
|
@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:state_pressed="true"
|
||||
android:drawable="@drawable/line"/>
|
||||
|
||||
<item
|
||||
android:drawable="@drawable/line"/>
|
||||
</selector>
|
@ -6,7 +6,7 @@
|
||||
android:orientation="vertical" >
|
||||
|
||||
<LinearLayout
|
||||
android:layout_height="4dip"
|
||||
android:layout_height="4dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:background="@drawable/drop_shadow" />
|
||||
|
||||
@ -21,7 +21,8 @@
|
||||
android:layout_height="64.0dip"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:layout_marginStart="6dp" />
|
||||
android:layout_marginStart="6dp"
|
||||
android:importantForAccessibility="no"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0.0dp"
|
||||
@ -62,7 +63,8 @@
|
||||
android:layout_weight="0.0"
|
||||
android:focusable="false"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="?attr/media_pause" />
|
||||
android:src="?attr/media_pause"
|
||||
android:contentDescription="@string/buttons.play"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
a:id="@+id/select_album_art"
|
||||
a:layout_width="160dip"
|
||||
a:layout_height="160dip"
|
||||
a:layout_alignParentLeft="true"
|
||||
a:layout_alignParentStart="true"
|
||||
a:layout_alignParentTop="true"
|
||||
a:layout_marginEnd="10dip"
|
||||
a:contentDescription="@null"
|
||||
|
@ -26,10 +26,10 @@
|
||||
a:layout_height="wrap_content"
|
||||
a:layout_gravity="left|center_vertical"
|
||||
a:layout_weight="1"
|
||||
a:drawablePadding="6dip"
|
||||
a:drawablePadding="4dip"
|
||||
a:ellipsize="end"
|
||||
a:paddingStart="4dip"
|
||||
a:paddingEnd="2dip"
|
||||
a:paddingStart="6dip"
|
||||
a:paddingEnd="4dip"
|
||||
a:singleLine="true"
|
||||
a:textAppearance="?android:attr/textAppearanceMedium"/>
|
||||
|
||||
@ -55,7 +55,8 @@
|
||||
a:layout_gravity="left|center_vertical"
|
||||
a:layout_weight="1"
|
||||
a:ellipsize="middle"
|
||||
a:paddingStart="4dip"
|
||||
a:paddingStart="1dip"
|
||||
a:paddingEnd="4dip"
|
||||
a:singleLine="true"
|
||||
a:textAppearance="?android:attr/textAppearanceSmall"/>
|
||||
|
||||
|
@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:a="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
>
|
||||
|
||||
<item
|
||||
a:id="@+id/main_shuffle"
|
||||
a:icon="?attr/media_shuffle"
|
||||
app:showAsAction="ifRoom|withText"
|
||||
a:title="@string/main.shuffle"/>
|
||||
|
||||
</menu>
|
@ -6,8 +6,6 @@
|
||||
<color name="cyan">#0099cc</color>
|
||||
<color name="navigation_header_light">#6200EE</color>
|
||||
<color name="navigation_header_dark">#BB86FC</color>
|
||||
<color name="ics_opaque">#8033b5e5</color>
|
||||
<color name="md__transparent">#00000000</color>
|
||||
<color name="translucent">#80000000</color>
|
||||
<color name="background_color_dark">#000000</color>
|
||||
<color name="background_color_grey">#333333</color>
|
||||
|
@ -339,8 +339,8 @@
|
||||
<string name="settings.view_refresh_4000">4 seconds</string>
|
||||
<string name="settings.view_refresh_4500">4.5 seconds</string>
|
||||
<string name="settings.view_refresh_5000">5 seconds</string>
|
||||
<string name="settings.wifi_required_summary">Only stream media if connected to Wi-Fi</string>
|
||||
<string name="settings.wifi_required_title">Wi-Fi Streaming Only</string>
|
||||
<string name="settings.wifi_required_summary">Only download media on unmetered connections</string>
|
||||
<string name="settings.wifi_required_title">Download on Wi-Fi only</string>
|
||||
<string name="song_details.all">%1$s%2$s</string>
|
||||
<string name="song_details.kbps">%d kbps</string>
|
||||
<string name="util.bytes_format.byte">0 B</string>
|
||||
|
@ -64,7 +64,7 @@
|
||||
<item name="list_selector_holo_selected">@drawable/list_selector_holo_dark_selected</item>
|
||||
</style>
|
||||
|
||||
<style name="UltrasonicTheme" parent="Theme.MaterialComponents">
|
||||
<style name="UltrasonicTheme.Dark" parent="Theme.MaterialComponents">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="color_background">@color/background_color_grey</item>
|
||||
|
@ -19,7 +19,7 @@
|
||||
a:title="@string/settings.server_scaling_title"
|
||||
app:iconSpaceReserved="false"/>
|
||||
<CheckBoxPreference
|
||||
a:defaultValue="true"
|
||||
a:defaultValue="false"
|
||||
a:key="displayBitrateWithArtist"
|
||||
a:summary="@string/settings.display_bitrate_summary"
|
||||
a:title="@string/settings.display_bitrate"
|
||||
|
Loading…
x
Reference in New Issue
Block a user