@@ -5,10 +5,13 @@ import androidx.compose.ui.util.fastFirstOrNull
5
5
import com.skyd.anivu.appContext
6
6
import com.skyd.anivu.base.BaseRepository
7
7
import com.skyd.anivu.ext.dataStore
8
+ import com.skyd.anivu.ext.splitByBlank
8
9
import com.skyd.anivu.ext.validateFileName
9
10
import com.skyd.anivu.model.bean.MediaBean
10
11
import com.skyd.anivu.model.bean.MediaGroupBean
11
12
import com.skyd.anivu.model.bean.MediaGroupBean.Companion.isDefaultGroup
13
+ import com.skyd.anivu.model.bean.article.ArticleWithEnclosureBean
14
+ import com.skyd.anivu.model.bean.feed.FeedBean
12
15
import com.skyd.anivu.model.db.dao.ArticleDao
13
16
import com.skyd.anivu.model.db.dao.FeedDao
14
17
import com.skyd.anivu.model.preference.appearance.media.MediaFileFilterPreference
@@ -20,10 +23,14 @@ import com.skyd.anivu.model.preference.behavior.media.MediaSubListSortByPreferen
20
23
import kotlinx.coroutines.Dispatchers
21
24
import kotlinx.coroutines.flow.Flow
22
25
import kotlinx.coroutines.flow.MutableSharedFlow
26
+ import kotlinx.coroutines.flow.asFlow
27
+ import kotlinx.coroutines.flow.collect
23
28
import kotlinx.coroutines.flow.combine
29
+ import kotlinx.coroutines.flow.debounce
24
30
import kotlinx.coroutines.flow.distinctUntilChanged
25
31
import kotlinx.coroutines.flow.filter
26
32
import kotlinx.coroutines.flow.first
33
+ import kotlinx.coroutines.flow.flatMapLatest
27
34
import kotlinx.coroutines.flow.flow
28
35
import kotlinx.coroutines.flow.flowOf
29
36
import kotlinx.coroutines.flow.flowOn
@@ -49,7 +56,7 @@ class MediaRepository @Inject constructor(
49
56
const val OLD_MEDIA_LIB_JSON_NAME = " group.json"
50
57
const val MEDIA_LIB_JSON_NAME = " MediaLib.json"
51
58
52
- private val mediaLibJsons = LruCache <String , MediaLibJson >(maxSize = 5 )
59
+ private val mediaLibJsons = LruCache <String , MediaLibJson >(maxSize = 10 )
53
60
54
61
private val refreshPath = MutableSharedFlow <String >(extraBufferCapacity = Int .MAX_VALUE )
55
62
}
@@ -169,6 +176,31 @@ class MediaRepository @Inject constructor(
169
176
}
170
177
}
171
178
179
+ private fun FileJson.toMediaBean (
180
+ path : String ,
181
+ articleWithEnclosure : ArticleWithEnclosureBean ? ,
182
+ feedBean : FeedBean ? ,
183
+ ): MediaBean ? {
184
+ val file = File (path, fileName)
185
+ if (! file.exists()) return null
186
+ val fileCount = if (file.isDirectory) {
187
+ runCatching { file.list()?.size }.getOrNull()?.run {
188
+ this - listOf (
189
+ File (file, MEDIA_LIB_JSON_NAME ).exists(),
190
+ File (file, FOLDER_INFO_JSON_NAME ).exists(),
191
+ ).count { it }
192
+ } ? : 0
193
+ } else 0
194
+
195
+ return MediaBean (
196
+ displayName = displayName,
197
+ file = file,
198
+ fileCount = fileCount,
199
+ articleWithEnclosure = articleWithEnclosure,
200
+ feedBean = feedBean,
201
+ )
202
+ }
203
+
172
204
fun requestGroups (path : String ): Flow <List <MediaGroupBean >> = merge(
173
205
flowOf(path), refreshFiles, refreshPath
174
206
).filter { it == path }.map {
@@ -205,21 +237,8 @@ class MediaRepository @Inject constructor(
205
237
).associateBy { it.feed.url }
206
238
207
239
jsons.mapNotNull { fileJson ->
208
- val file = File (path, fileJson.fileName)
209
- if (! file.exists()) return @mapNotNull null
210
- val fileCount = if (file.isDirectory) {
211
- runCatching { file.list()?.size }.getOrNull()?.run {
212
- this - listOf (
213
- File (file, MEDIA_LIB_JSON_NAME ).exists(),
214
- File (file, FOLDER_INFO_JSON_NAME ).exists(),
215
- ).count { it }
216
- } ? : 0
217
- } else 0
218
-
219
- MediaBean (
220
- displayName = fileJson.displayName,
221
- file = file,
222
- fileCount = fileCount,
240
+ fileJson.toMediaBean(
241
+ path = path,
223
242
articleWithEnclosure = articleMap[fileJson.articleId]?.articleWithEnclosure,
224
243
feedBean = feedMap[fileJson.feedUrl]?.feed
225
244
? : articleMap[fileJson.articleId]?.feed,
@@ -267,6 +286,51 @@ class MediaRepository @Inject constructor(
267
286
if (sortAsc) list else list.reversed()
268
287
}.flowOn(Dispatchers .IO )
269
288
289
+ fun search (
290
+ path : String ,
291
+ query : String ,
292
+ recursive : Boolean = false,
293
+ ): Flow <List <MediaBean >> = flowOf(
294
+ query.trim() to recursive
295
+ ).flatMapLatest { (query, recursive) ->
296
+ merge(
297
+ flowOf(path), refreshFiles, refreshPath
298
+ ).debounce(70 ).filter { it == path }.map {
299
+ val queries = query.splitByBlank()
300
+
301
+ val fileJsons = mutableListOf<FileJson >()
302
+ File (path).walkBottomUp().onEnter { dir ->
303
+ dir.path == path || recursive
304
+ }.filter { it.isDirectory }.asFlow().map {
305
+ val mediaLibJson = getOrReadMediaLibJson(it.path)
306
+ fileJsons + = mediaLibJson.files
307
+ }.collect()
308
+
309
+ val articleMap = articleDao.getArticleListByIds(
310
+ fileJsons.mapNotNull { it.articleId }
311
+ ).associateBy { it.articleWithEnclosure.article.articleId }
312
+ val feedMap = feedDao.getFeedsIn(
313
+ fileJsons.mapNotNull { it.feedUrl }
314
+ ).associateBy { it.feed.url }
315
+
316
+ fileJsons.filter { file ->
317
+ queries.any {
318
+ it in file.fileName ||
319
+ it in file.displayName.orEmpty() ||
320
+ it in file.feedUrl.orEmpty() ||
321
+ it in file.articleLink.orEmpty()
322
+ }
323
+ }.mapNotNull { fileJson ->
324
+ fileJson.toMediaBean(
325
+ path = path,
326
+ articleWithEnclosure = articleMap[fileJson.articleId]?.articleWithEnclosure,
327
+ feedBean = feedMap[fileJson.feedUrl]?.feed
328
+ ? : articleMap[fileJson.articleId]?.feed,
329
+ )
330
+ }
331
+ }
332
+ }.flowOn(Dispatchers .IO )
333
+
270
334
fun deleteFile (file : File ): Flow <Boolean > {
271
335
return flow {
272
336
val path = file.parentFile!! .path
@@ -322,11 +386,12 @@ class MediaRepository @Inject constructor(
322
386
323
387
val realGroupName = if (group.isDefaultGroup()) null else group.name
324
388
val realArticleId = articleId.takeIf { ! it.isNullOrBlank() }
325
- val realDisplayName = displayName.takeIf { ! it.isNullOrBlank() }
326
- val article = articleId?.let { articleDao.getArticleWithFeed(it).first() }
389
+ val article = realArticleId?.let { articleDao.getArticleWithFeed(it).first() }
327
390
val articleLink = article?.articleWithEnclosure?.article?.link
328
391
val articleGuid = article?.articleWithEnclosure?.article?.guid
329
392
val feedUrl = article?.feed?.url
393
+ val realDisplayName = displayName.takeIf { ! it.isNullOrBlank() }
394
+ ? : article?.articleWithEnclosure?.article?.title
330
395
331
396
val path = file.parentFile!! .path
332
397
var mediaLibJson = getOrReadMediaLibJson(path = path)
@@ -403,7 +468,8 @@ class MediaRepository @Inject constructor(
403
468
404
469
val realGroupName = if (group.isDefaultGroup()) null else group.name
405
470
val realFeedUrl = feedUrl.takeIf { ! it.isNullOrBlank() }
406
- val realDisplayName = displayName.takeIf { ! it.isNullOrBlank() }
471
+ val feed = realFeedUrl?.let { feedDao.getFeed(it) }
472
+ val realDisplayName = displayName.takeIf { ! it.isNullOrBlank() } ? : feed?.feed?.title
407
473
408
474
val path = file.parentFile!! .path
409
475
var mediaLibJson = getOrReadMediaLibJson(path = path)
0 commit comments