refactor: remove dead download code
This commit is contained in:
@@ -1817,11 +1817,6 @@ class DownloadManagerService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all downloads with a specific status
|
||||
Stream<List<DownloadedMediaItem>> watchDownloadsByStatus(DownloadStatus status) {
|
||||
return (_database.select(_database.downloadedMedia)..where((t) => t.status.equals(status.index))).watch();
|
||||
}
|
||||
|
||||
/// Get all downloaded media items (for loading persisted data)
|
||||
Future<List<DownloadedMediaItem>> getAllDownloads() {
|
||||
return _database.select(_database.downloadedMedia).get();
|
||||
@@ -1879,18 +1874,6 @@ class DownloadManagerService {
|
||||
await _apiCache.pinForOffline(serverId, ratingKey);
|
||||
}
|
||||
|
||||
/// Cache children (seasons or episodes) in the API response format
|
||||
Future<void> cacheChildrenForOffline(String serverId, String parentRatingKey, List<PlexMetadata> children) async {
|
||||
final endpoint = '/library/metadata/$parentRatingKey/children';
|
||||
|
||||
// Build a response structure that matches the Plex API format
|
||||
final cachedResponse = {
|
||||
'MediaContainer': {'Metadata': children.map((c) => c.toJson()).toList()},
|
||||
};
|
||||
|
||||
await _apiCache.put(serverId, endpoint, cachedResponse);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_disposed = true;
|
||||
for (final timer in _progressDebounceTimers.values) {
|
||||
|
||||
@@ -84,12 +84,6 @@ class DownloadStorageService {
|
||||
return dir.path;
|
||||
}
|
||||
|
||||
/// Get default download path (for "Reset to Default" functionality)
|
||||
Future<String> getDefaultDownloadPath() async {
|
||||
final baseDir = await _getBaseAppDir();
|
||||
return path.join(baseDir.path, 'downloads');
|
||||
}
|
||||
|
||||
/// Check if a directory is writable
|
||||
Future<bool> isDirectoryWritable(Directory dir) async {
|
||||
try {
|
||||
@@ -194,12 +188,6 @@ class DownloadStorageService {
|
||||
return path.join(mediaDir.path, 'video.$extension');
|
||||
}
|
||||
|
||||
/// Get artwork file path (poster, art, thumb)
|
||||
Future<String> getArtworkPath(String serverId, String ratingKey, String artworkType) async {
|
||||
final mediaDir = await getMediaDirectory(serverId, ratingKey);
|
||||
return path.join(mediaDir.path, '$artworkType.jpg');
|
||||
}
|
||||
|
||||
/// Get subtitles directory
|
||||
Future<Directory> getSubtitlesDirectory(String serverId, String ratingKey) async {
|
||||
final mediaDir = await getMediaDirectory(serverId, ratingKey);
|
||||
@@ -272,12 +260,6 @@ class DownloadStorageService {
|
||||
return path.join(movieDir.path, '$fileName.$extension');
|
||||
}
|
||||
|
||||
/// Get movie artwork path: .../Movie Name (YYYY)/{artworkType}.jpg
|
||||
Future<String> getMovieArtworkPath(PlexMetadata movie, String artworkType) async {
|
||||
final movieDir = await getMovieDirectory(movie);
|
||||
return path.join(movieDir.path, '$artworkType.jpg');
|
||||
}
|
||||
|
||||
/// Get show directory: downloads/TV Shows/{Show Name} ({Year})/
|
||||
/// [showYear]: Pass the show's premiere year explicitly (for episodes, the episode's
|
||||
/// year may differ from the show's year). If not provided, uses metadata.year.
|
||||
@@ -287,12 +269,6 @@ class DownloadStorageService {
|
||||
return _ensureDirectoryExists(Directory(path.join(baseDir.path, 'TV Shows', showFolder)));
|
||||
}
|
||||
|
||||
/// Get show artwork path: downloads/TV Shows/{Show}/poster.jpg
|
||||
Future<String> getShowArtworkPath(PlexMetadata metadata, String artworkType, {int? showYear}) async {
|
||||
final showDir = await getShowDirectory(metadata, showYear: showYear);
|
||||
return path.join(showDir.path, '$artworkType.jpg');
|
||||
}
|
||||
|
||||
/// Get season directory: .../TV Shows/{Show}/Season {XX}/
|
||||
/// [showYear]: Pass the show's premiere year (not episode or season year)
|
||||
Future<Directory> getSeasonDirectory(PlexMetadata metadata, {int? showYear}) async {
|
||||
@@ -301,12 +277,6 @@ class DownloadStorageService {
|
||||
return _ensureDirectoryExists(Directory(path.join(showDir.path, 'Season $seasonNum')));
|
||||
}
|
||||
|
||||
/// Get season artwork path: .../Season XX/poster.jpg
|
||||
Future<String> getSeasonArtworkPath(PlexMetadata metadata, String artworkType, {int? showYear}) async {
|
||||
final seasonDir = await getSeasonDirectory(metadata, showYear: showYear);
|
||||
return path.join(seasonDir.path, '$artworkType.jpg');
|
||||
}
|
||||
|
||||
/// Get base path info for episode files (season directory path and formatted filename).
|
||||
/// [showYear]: Pass the show's premiere year (not episode year)
|
||||
Future<({String seasonDirPath, String fileName})> _getEpisodeBasePath(PlexMetadata episode, {int? showYear}) async {
|
||||
@@ -356,14 +326,6 @@ class DownloadStorageService {
|
||||
return path.join(subsDir.path, '$trackId.$extension');
|
||||
}
|
||||
|
||||
/// Delete all files for a media item
|
||||
Future<void> deleteMediaFiles(String serverId, String ratingKey) async {
|
||||
final mediaDir = await getMediaDirectory(serverId, ratingKey);
|
||||
if (await mediaDir.exists()) {
|
||||
await mediaDir.delete(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert an absolute file path to a relative path (for database storage)
|
||||
/// This ensures paths remain valid across app reinstalls on iOS where
|
||||
/// the container UUID can change.
|
||||
@@ -469,80 +431,6 @@ class DownloadStorageService {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/// Calculate total storage used by downloads
|
||||
Future<int> getTotalStorageUsed() async {
|
||||
final baseDir = await getDownloadsDirectory();
|
||||
return _calculateDirectorySize(baseDir);
|
||||
}
|
||||
|
||||
Future<int> _calculateDirectorySize(Directory dir) async {
|
||||
int size = 0;
|
||||
if (!await dir.exists()) return size;
|
||||
|
||||
await for (var entity in dir.list(recursive: true, followLinks: false)) {
|
||||
if (entity is File) {
|
||||
try {
|
||||
size += await entity.length();
|
||||
} catch (_) {
|
||||
// Ignore errors reading file size
|
||||
}
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/// Format bytes to human readable string
|
||||
static String formatBytes(int bytes) => ByteFormatter.formatBytes(bytes);
|
||||
|
||||
// ============================================================
|
||||
// SAF (Storage Access Framework) SUPPORT FOR ANDROID
|
||||
// ============================================================
|
||||
|
||||
/// Get temporary cache directory for initial downloads
|
||||
/// Files are downloaded here first, then copied to SAF if using SAF mode
|
||||
Future<Directory> getCacheDownloadDirectory() async {
|
||||
final cacheDir = await getApplicationDocumentsDirectory();
|
||||
return _ensureDirectoryExists(Directory(path.join(cacheDir.path, '.download_cache')));
|
||||
}
|
||||
|
||||
/// Get temporary file path for downloading (before copying to SAF)
|
||||
Future<String> getTempDownloadPath(String fileName) async {
|
||||
final cacheDir = await getCacheDownloadDirectory();
|
||||
return path.join(cacheDir.path, fileName);
|
||||
}
|
||||
|
||||
/// Get the MIME type for a file extension
|
||||
String getMimeType(String extension) {
|
||||
switch (extension.toLowerCase()) {
|
||||
case 'mp4':
|
||||
return 'video/mp4';
|
||||
case 'mkv':
|
||||
return 'video/x-matroska';
|
||||
case 'm4v':
|
||||
return 'video/x-m4v';
|
||||
case 'avi':
|
||||
return 'video/x-msvideo';
|
||||
case 'ogv':
|
||||
return 'video/ogg';
|
||||
case 'webm':
|
||||
return 'video/webm';
|
||||
case 'srt':
|
||||
return 'application/x-subrip';
|
||||
case 'vtt':
|
||||
return 'text/vtt';
|
||||
case 'ass':
|
||||
case 'ssa':
|
||||
return 'text/x-ssa';
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
return 'image/jpeg';
|
||||
case 'png':
|
||||
return 'image/png';
|
||||
default:
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
}
|
||||
|
||||
/// Get path components for SAF based on media type
|
||||
/// Returns list of directory names to create under the SAF base
|
||||
List<String> getMovieSafPathComponents(PlexMetadata movie) {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:saf_util/saf_util.dart';
|
||||
import '../utils/platform_detector.dart';
|
||||
import 'package:saf_util/saf_util_platform_interface.dart';
|
||||
import 'package:saf_stream/saf_stream.dart';
|
||||
|
||||
/// Handles Storage Access Framework (SAF) operations for Android
|
||||
class SafStorageService {
|
||||
@@ -14,7 +12,6 @@ class SafStorageService {
|
||||
SafStorageService._();
|
||||
|
||||
final SafUtil _safUtil = SafUtil();
|
||||
final SafStream _safStream = SafStream();
|
||||
|
||||
/// Check if SAF is available (Android only)
|
||||
bool get isAvailable => Platform.isAndroid;
|
||||
@@ -35,28 +32,6 @@ class SafStorageService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if we have persisted access to a URI
|
||||
Future<bool> hasPersistedPermission(String contentUri) async {
|
||||
if (!isAvailable) return false;
|
||||
try {
|
||||
return await _safUtil.hasPersistedPermission(contentUri, checkRead: true, checkWrite: true);
|
||||
} catch (e) {
|
||||
debugPrint('SAF hasPersistedPermission error: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get document file info for a URI
|
||||
Future<SafDocumentFile?> getDocumentFile(String contentUri, {bool isDir = true}) async {
|
||||
if (!isAvailable) return null;
|
||||
try {
|
||||
return await _safUtil.documentFileFromUri(contentUri, isDir);
|
||||
} catch (e) {
|
||||
debugPrint('SAF getDocumentFile error: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a subdirectory in a SAF directory
|
||||
/// Returns the URI of the created directory
|
||||
Future<String?> createDirectory(String parentUri, String name) async {
|
||||
@@ -70,17 +45,6 @@ class SafStorageService {
|
||||
}
|
||||
}
|
||||
|
||||
/// List files in a SAF directory
|
||||
Future<List<SafDocumentFile>> listDirectory(String contentUri) async {
|
||||
if (!isAvailable) return [];
|
||||
try {
|
||||
return await _safUtil.list(contentUri);
|
||||
} catch (e) {
|
||||
debugPrint('SAF listDirectory error: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a child file/directory in a SAF directory
|
||||
Future<SafDocumentFile?> getChild(String parentUri, String name) async {
|
||||
if (!isAvailable) return null;
|
||||
@@ -92,30 +56,6 @@ class SafStorageService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete a file or directory in SAF
|
||||
Future<bool> delete(String contentUri, {bool isDir = false}) async {
|
||||
if (!isAvailable) return false;
|
||||
try {
|
||||
await _safUtil.delete(contentUri, isDir);
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('SAF delete error: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a display name for a SAF URI (for UI purposes)
|
||||
Future<String?> getDisplayName(String contentUri) async {
|
||||
if (!isAvailable) return null;
|
||||
try {
|
||||
final doc = await _safUtil.documentFileFromUri(contentUri, true);
|
||||
return doc?.name;
|
||||
} catch (e) {
|
||||
debugPrint('SAF getDisplayName error: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Create nested directories in a SAF directory
|
||||
/// Returns the URI of the deepest directory
|
||||
Future<String?> createNestedDirectories(String parentUri, List<String> pathComponents) async {
|
||||
@@ -129,43 +69,4 @@ class SafStorageService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Write bytes directly to a SAF file
|
||||
/// Returns the SAF URI of the created file, or null on failure
|
||||
Future<String?> writeFileBytes(String directoryUri, String fileName, String mimeType, Uint8List bytes) async {
|
||||
if (!isAvailable) return null;
|
||||
try {
|
||||
final result = await _safStream.writeFileBytes(directoryUri, fileName, mimeType, bytes);
|
||||
return result.uri.toString();
|
||||
} catch (e) {
|
||||
debugPrint('SAF writeFileBytes error: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Read bytes from a SAF file
|
||||
Future<Uint8List?> readFileBytes(String fileUri) async {
|
||||
if (!isAvailable) return null;
|
||||
try {
|
||||
return await _safStream.readFileBytes(fileUri);
|
||||
} catch (e) {
|
||||
debugPrint('SAF readFileBytes error: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a file exists in a SAF directory
|
||||
Future<bool> fileExists(String parentUri, String fileName) async {
|
||||
if (!isAvailable) return false;
|
||||
try {
|
||||
final child = await _safUtil.child(parentUri, [fileName]);
|
||||
return child != null;
|
||||
} catch (e) {
|
||||
debugPrint('SAF fileExists error: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the content URI for a file that should be readable by MediaStore/media players
|
||||
/// For SAF files, this returns the same URI as input (content:// URIs are already readable)
|
||||
String getReadableUri(String safUri) => safUri;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user