mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-15 23:59:08 +00:00
fix: remove unused code
This commit is contained in:
@@ -1,330 +0,0 @@
|
||||
# Download System Refactor - Summary
|
||||
|
||||
## Overview
|
||||
|
||||
The download system has been completely refactored to use a new native iOS module for background downloads, replacing the third-party library with a custom, streamlined solution.
|
||||
|
||||
## What Was Done
|
||||
|
||||
### 1. Created New Native Module (`BackgroundDownloader`)
|
||||
|
||||
**Location**: `modules/background-downloader/`
|
||||
|
||||
A complete Expo native module for iOS background file downloads:
|
||||
|
||||
- **Swift Implementation**: `BackgroundDownloaderModule.swift`
|
||||
- Uses `NSURLSession` with background configuration
|
||||
- Implements `URLSessionDownloadDelegate` for progress tracking
|
||||
- Handles iOS background session events via AppDelegate subscriber
|
||||
- Session ID: `com.fredrikburmester.streamyfin.backgrounddownloader`
|
||||
|
||||
- **TypeScript Interface**: Full type-safe API with events
|
||||
- `startDownload(url, destinationPath?)` - Start a download
|
||||
- `cancelDownload(taskId)` - Cancel a specific download
|
||||
- `cancelAllDownloads()` - Cancel all downloads
|
||||
- `getActiveDownloads()` - List active downloads
|
||||
|
||||
- **Events**:
|
||||
- `onDownloadProgress` - Progress updates with bytes/percentage
|
||||
- `onDownloadComplete` - Completion with file path
|
||||
- `onDownloadError` - Error handling
|
||||
- `onDownloadStarted` - Download initiation confirmation
|
||||
|
||||
- **Documentation**: Complete README with usage examples
|
||||
|
||||
### 2. Rewrote DownloadProvider
|
||||
|
||||
**Location**: `providers/DownloadProvider.tsx`
|
||||
|
||||
A simplified, focused implementation:
|
||||
|
||||
**Features Included**:
|
||||
- ✅ Video file downloads with progress tracking
|
||||
- ✅ Background download support
|
||||
- ✅ Database persistence (same format as before)
|
||||
- ✅ Movies and TV episodes support
|
||||
- ✅ Download notifications (success/error)
|
||||
- ✅ File deletion and management
|
||||
- ✅ Size calculation
|
||||
- ✅ Same context API for backward compatibility
|
||||
|
||||
**Features Removed (for simplicity)**:
|
||||
- ❌ Trickplay image downloads
|
||||
- ❌ Subtitle downloads
|
||||
- ❌ Queue management with concurrent limits
|
||||
- ❌ Pause/Resume (can be added back easily)
|
||||
- ❌ Download speed/ETA calculations
|
||||
- ❌ Cache directory management
|
||||
|
||||
**Key Improvements**:
|
||||
- Event-driven architecture (no polling)
|
||||
- Better background handling via native module
|
||||
- Cleaner, more maintainable code
|
||||
- Proper TypeScript typing throughout
|
||||
- Simplified state management
|
||||
|
||||
### 3. Preserved Old Implementation
|
||||
|
||||
**Location**: `providers/DownloadProvider.deprecated.tsx`
|
||||
|
||||
The old implementation has been preserved for reference but should not be used.
|
||||
|
||||
### 4. Documentation
|
||||
|
||||
Created comprehensive documentation:
|
||||
|
||||
- **Module README**: `modules/background-downloader/README.md`
|
||||
- API reference
|
||||
- Usage examples
|
||||
- Implementation details
|
||||
|
||||
- **Migration Guide**: `providers/Downloads/MIGRATION.md`
|
||||
- What changed
|
||||
- API compatibility matrix
|
||||
- Migration steps
|
||||
- Troubleshooting guide
|
||||
|
||||
- **System README**: `providers/Downloads/README.md`
|
||||
- Architecture overview
|
||||
- Type definitions
|
||||
- Usage examples
|
||||
- File storage details
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Background Download Implementation
|
||||
|
||||
The native module uses iOS's recommended approach:
|
||||
|
||||
1. **Background URLSession**: Persistent identifier ensures downloads continue
|
||||
2. **Delegate Pattern**: Progress and completion via delegate callbacks
|
||||
3. **AppDelegate Integration**: Handles system wake-ups for download events
|
||||
4. **Completion Handler**: Properly signals iOS when background work is done
|
||||
|
||||
### State Management
|
||||
|
||||
```typescript
|
||||
// Active downloads tracked in Jotai atom
|
||||
const processesAtom = atom<JobStatus[]>([]);
|
||||
|
||||
// Task ID to Process ID mapping for event correlation
|
||||
const taskMap = Map<number, string>();
|
||||
|
||||
// Persistent database in MMKV
|
||||
storage.set('downloads.v2.json', JSON.stringify(database));
|
||||
```
|
||||
|
||||
### Download Flow
|
||||
|
||||
```
|
||||
User initiates download
|
||||
↓
|
||||
Create JobStatus & add to processes
|
||||
↓
|
||||
Generate destination path
|
||||
↓
|
||||
Start native BackgroundDownloader
|
||||
↓
|
||||
Map taskId ↔ processId
|
||||
↓
|
||||
Receive progress events → Update UI
|
||||
↓
|
||||
Receive completion event
|
||||
↓
|
||||
Move file to permanent location
|
||||
↓
|
||||
Save to database
|
||||
↓
|
||||
Send notification
|
||||
↓
|
||||
Clean up process
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### iOS Background Modes
|
||||
|
||||
Already configured in `app.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"ios": {
|
||||
"infoPlist": {
|
||||
"UIBackgroundModes": ["audio", "fetch"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The "fetch" mode enables background URLSession downloads.
|
||||
|
||||
### Module Integration
|
||||
|
||||
The module is:
|
||||
- ✅ Auto-linked via Expo
|
||||
- ✅ Exported from `modules/index.ts`
|
||||
- ✅ Type-safe with full TypeScript support
|
||||
- ✅ Registered as AppDelegate subscriber
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
### None for Normal Usage
|
||||
|
||||
The public API remains the same for the most common operations:
|
||||
|
||||
```typescript
|
||||
const {
|
||||
startBackgroundDownload,
|
||||
cancelDownload,
|
||||
getDownloadedItems,
|
||||
deleteFile,
|
||||
processes
|
||||
} = useDownload();
|
||||
```
|
||||
|
||||
### No-op Methods
|
||||
|
||||
These methods exist but do nothing (for compatibility):
|
||||
- `pauseDownload()`
|
||||
- `resumeDownload()`
|
||||
- `startDownload()` (use `startBackgroundDownload` instead)
|
||||
- `deleteFileByType()`
|
||||
- `cleanCacheDirectory()`
|
||||
- `updateDownloadedItem()`
|
||||
- `dumpDownloadDiagnostics()`
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
Before deployment, test:
|
||||
|
||||
- [ ] Download a movie
|
||||
- [ ] Download a TV episode
|
||||
- [ ] Download multiple items simultaneously
|
||||
- [ ] Cancel an active download
|
||||
- [ ] Delete a downloaded item
|
||||
- [ ] View list of downloads
|
||||
- [ ] Background the app during download
|
||||
- [ ] Force quit and restart (download should be cancelled)
|
||||
- [ ] Verify notifications appear
|
||||
- [ ] Check file integrity and playback
|
||||
- [ ] Verify database persistence
|
||||
- [ ] Check storage calculations
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Immediate
|
||||
|
||||
1. **Rebuild iOS app**:
|
||||
```bash
|
||||
npx expo prebuild -p ios
|
||||
cd ios && pod install && cd ..
|
||||
npx expo run:ios
|
||||
```
|
||||
|
||||
2. **Test thoroughly**: Use the testing checklist above
|
||||
|
||||
3. **Monitor for issues**: Check console logs and user reports
|
||||
|
||||
### Future Enhancements
|
||||
|
||||
Priority features to add back:
|
||||
|
||||
1. **Pause/Resume** (High Priority)
|
||||
- Easy to implement with NSURLSession
|
||||
- User-requested feature
|
||||
|
||||
2. **Queue Management** (Medium Priority)
|
||||
- Concurrent download limits
|
||||
- Automatic queue processing
|
||||
|
||||
3. **Progress Persistence** (Medium Priority)
|
||||
- Resume interrupted downloads after app restart
|
||||
- Save download state to database
|
||||
|
||||
4. **Trickplay & Subtitles** (Low Priority)
|
||||
- Re-add auxiliary file downloads
|
||||
- Integrate with video playback
|
||||
|
||||
5. **Download Analytics** (Low Priority)
|
||||
- Speed calculation
|
||||
- ETA estimation
|
||||
- Failure rate tracking
|
||||
|
||||
## Known Limitations
|
||||
|
||||
1. **iOS Only**: Android support not yet implemented
|
||||
2. **No Pause**: Cannot pause/resume downloads yet
|
||||
3. **No Queue**: All downloads start immediately
|
||||
4. **Force Quit**: Downloads cancelled if app is force-quit (iOS limitation)
|
||||
5. **No Persistence**: Downloads lost if app crashes or is terminated
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
Over the old system:
|
||||
|
||||
- **Better Background Support**: True iOS background sessions
|
||||
- **Event-Driven**: No polling, immediate updates
|
||||
- **Lower Overhead**: Removed unused features
|
||||
- **Native Integration**: Tighter iOS system integration
|
||||
- **Cleaner Code**: Easier to maintain and extend
|
||||
|
||||
## Dependencies Changed
|
||||
|
||||
### Removed
|
||||
- `@kesha-antonov/react-native-background-downloader`
|
||||
|
||||
### Added
|
||||
- Custom `BackgroundDownloader` native module (local)
|
||||
|
||||
No external dependencies added, reducing maintenance burden.
|
||||
|
||||
## File Changes Summary
|
||||
|
||||
### New Files
|
||||
- `modules/background-downloader/` (entire module)
|
||||
- `providers/Downloads/MIGRATION.md`
|
||||
- `providers/Downloads/README.md`
|
||||
- `DOWNLOAD_SYSTEM_REFACTOR.md` (this file)
|
||||
|
||||
### Modified Files
|
||||
- `modules/index.ts` (exported new module)
|
||||
- `providers/DownloadProvider.tsx` (complete rewrite)
|
||||
|
||||
### Renamed Files
|
||||
- `providers/DownloadProvider.tsx` → `providers/DownloadProvider.deprecated.tsx`
|
||||
|
||||
### Unchanged
|
||||
- `providers/Downloads/types.ts` (types remain the same)
|
||||
- Database format and storage location
|
||||
- Public API for most common operations
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If issues arise:
|
||||
|
||||
1. Rename `DownloadProvider.deprecated.tsx` back to `DownloadProvider.tsx`
|
||||
2. Remove the `background-downloader` module export from `modules/index.ts`
|
||||
3. Re-install `@kesha-antonov/react-native-background-downloader`
|
||||
4. Rebuild the app
|
||||
|
||||
Note: The database format is unchanged, so existing downloads will work with either version.
|
||||
|
||||
## Success Metrics
|
||||
|
||||
The refactor is successful if:
|
||||
|
||||
- ✅ Downloads work in foreground
|
||||
- ✅ Downloads continue in background
|
||||
- ✅ Progress updates are accurate
|
||||
- ✅ Notifications work correctly
|
||||
- ✅ Files are saved and playable
|
||||
- ✅ No crashes or memory leaks
|
||||
- ✅ Performance is equal or better
|
||||
- ✅ Code is cleaner and more maintainable
|
||||
|
||||
## Conclusion
|
||||
|
||||
This refactor provides a solid foundation for the download system moving forward. The native module approach gives us full control over the download experience and makes it easier to add features in the future.
|
||||
|
||||
The simplified DownloadProvider focuses on core functionality while maintaining API compatibility, making this a low-risk, high-reward change.
|
||||
|
||||
@@ -6,9 +6,6 @@ module.exports = ({ config }) => {
|
||||
"react-native-google-cast",
|
||||
{ useDefaultExpandedMediaControls: true },
|
||||
]);
|
||||
|
||||
// Add the background downloader plugin only for non-TV builds
|
||||
config.plugins.push("./plugins/withRNBackgroundDownloader.js");
|
||||
}
|
||||
return {
|
||||
android: {
|
||||
|
||||
@@ -6,7 +6,6 @@ import { t } from "i18next";
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Platform,
|
||||
TouchableOpacity,
|
||||
type TouchableOpacityProps,
|
||||
View,
|
||||
@@ -28,30 +27,10 @@ interface DownloadCardProps extends TouchableOpacityProps {
|
||||
}
|
||||
|
||||
export const DownloadCard = ({ process, ...props }: DownloadCardProps) => {
|
||||
const { pauseDownload, resumeDownload, removeProcess } = useDownload();
|
||||
const { removeProcess } = useDownload();
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const handlePause = async () => {
|
||||
try {
|
||||
await pauseDownload();
|
||||
toast.success(t("home.downloads.toasts.download_paused"));
|
||||
} catch (error) {
|
||||
console.error("Error pausing download:", error);
|
||||
toast.error(t("home.downloads.toasts.could_not_pause_download"));
|
||||
}
|
||||
};
|
||||
|
||||
const handleResume = async () => {
|
||||
try {
|
||||
await resumeDownload();
|
||||
toast.success(t("home.downloads.toasts.download_resumed"));
|
||||
} catch (error) {
|
||||
console.error("Error resuming download:", error);
|
||||
toast.error(t("home.downloads.toasts.could_not_resume_download"));
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
try {
|
||||
await removeProcess(id);
|
||||
@@ -118,22 +97,6 @@ export const DownloadCard = ({ process, ...props }: DownloadCardProps) => {
|
||||
|
||||
{/* Action buttons in bottom right corner */}
|
||||
<View className='absolute bottom-2 right-2 flex flex-row items-center space-x-2 z-10'>
|
||||
{process.status === "downloading" && Platform.OS !== "ios" && (
|
||||
<TouchableOpacity
|
||||
onPress={() => handlePause()}
|
||||
className='p-2 bg-neutral-800 rounded-full'
|
||||
>
|
||||
<Ionicons name='pause' size={20} color='white' />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{process.status === "paused" && Platform.OS !== "ios" && (
|
||||
<TouchableOpacity
|
||||
onPress={() => handleResume()}
|
||||
className='p-2 bg-neutral-800 rounded-full'
|
||||
>
|
||||
<Ionicons name='play' size={20} color='white' />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
onPress={() => handleDelete(process.id)}
|
||||
className='p-2 bg-neutral-800 rounded-full'
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
const { withAppDelegate, withXcodeProject } = require("expo/config-plugins");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
/** @param {import("expo/config-plugins").ExpoConfig} config */
|
||||
function withRNBackgroundDownloader(config) {
|
||||
/* 1️⃣ Add handleEventsForBackgroundURLSession to AppDelegate.swift */
|
||||
config = withAppDelegate(config, (mod) => {
|
||||
const tag = "handleEventsForBackgroundURLSession";
|
||||
if (!mod.modResults.contents.includes(tag)) {
|
||||
mod.modResults.contents = mod.modResults.contents.replace(
|
||||
/\}\s*$/, // insert before final }
|
||||
`
|
||||
func application(
|
||||
_ application: UIApplication,
|
||||
handleEventsForBackgroundURLSession identifier: String,
|
||||
completionHandler: @escaping () -> Void
|
||||
) {
|
||||
RNBackgroundDownloader.setCompletionHandlerWithIdentifier(identifier, completionHandler: completionHandler)
|
||||
}
|
||||
}`,
|
||||
);
|
||||
}
|
||||
return mod;
|
||||
});
|
||||
|
||||
/* 2️⃣ Ensure bridging header exists & is attached to *every* app target */
|
||||
config = withXcodeProject(config, (mod) => {
|
||||
const project = mod.modResults;
|
||||
const projectName = config.name || "App";
|
||||
// Fix: Go up one more directory to get to ios/, not ios/ProjectName.xcodeproj/
|
||||
const iosDir = path.dirname(path.dirname(project.filepath));
|
||||
const headerRel = `${projectName}/${projectName}-Bridging-Header.h`;
|
||||
const headerAbs = path.join(iosDir, headerRel);
|
||||
|
||||
// create / append import if missing
|
||||
let headerText = "";
|
||||
try {
|
||||
headerText = fs.readFileSync(headerAbs, "utf8");
|
||||
} catch (error) {
|
||||
if (error.code !== "ENOENT") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
if (!headerText.includes("RNBackgroundDownloader.h")) {
|
||||
fs.mkdirSync(path.dirname(headerAbs), { recursive: true });
|
||||
fs.appendFileSync(headerAbs, '#import "RNBackgroundDownloader.h"\n');
|
||||
}
|
||||
|
||||
// Expo 53's xcode‑js doesn't expose pbxTargets().
|
||||
// Setting the property once at the project level is sufficient.
|
||||
["Debug", "Release"].forEach((cfg) => {
|
||||
// Use the detected projectName to set the bridging header path instead of a hardcoded value
|
||||
const bridgingHeaderPath = `${projectName}/${projectName}-Bridging-Header.h`;
|
||||
project.updateBuildProperty(
|
||||
"SWIFT_OBJC_BRIDGING_HEADER",
|
||||
bridgingHeaderPath,
|
||||
cfg,
|
||||
);
|
||||
});
|
||||
|
||||
return mod;
|
||||
});
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
module.exports = withRNBackgroundDownloader;
|
||||
@@ -135,8 +135,6 @@ function useDownloadProvider() {
|
||||
appSizeUsage,
|
||||
// Deprecated/not implemented in simple version
|
||||
startDownload: async () => {},
|
||||
pauseDownload: async () => {},
|
||||
resumeDownload: async () => {},
|
||||
cleanCacheDirectory: async () => {},
|
||||
updateDownloadedItem: () => {},
|
||||
dumpDownloadDiagnostics: async () => "",
|
||||
@@ -161,8 +159,6 @@ export function useDownload() {
|
||||
cancelDownload: async () => {},
|
||||
triggerRefresh: () => {},
|
||||
startDownload: async () => {},
|
||||
pauseDownload: async () => {},
|
||||
resumeDownload: async () => {},
|
||||
getDownloadedItemSize: () => 0,
|
||||
getDownloadedItemById: () => undefined,
|
||||
APP_CACHE_DOWNLOAD_DIRECTORY: "",
|
||||
|
||||
@@ -113,7 +113,6 @@ export type JobStatus = {
|
||||
/** Current status of the download job */
|
||||
status:
|
||||
| "downloading" // The job is actively downloading
|
||||
| "paused" // The job is paused
|
||||
| "error" // The job encountered an error
|
||||
| "pending" // The job is waiting to start
|
||||
| "completed" // The job has finished downloading
|
||||
@@ -133,14 +132,4 @@ export type JobStatus = {
|
||||
/** Estimated total size of the download in bytes (optional) this is used when we
|
||||
* download transcoded content because we don't know the size of the file until it's downloaded */
|
||||
estimatedTotalSizeBytes?: number;
|
||||
/** Timestamp when the download was paused (optional) */
|
||||
pausedAt?: Date;
|
||||
/** Progress percentage when download was paused (optional) */
|
||||
pausedProgress?: number;
|
||||
/** Bytes downloaded when download was paused (optional) */
|
||||
pausedBytes?: number;
|
||||
/** Bytes downloaded in the current session (since last resume). Used for session-only speed calculation. */
|
||||
lastSessionBytes?: number;
|
||||
/** Timestamp when the session-only bytes were last updated. */
|
||||
lastSessionUpdateTime?: Date;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user