fix: remove unused code

This commit is contained in:
Fredrik Burmester
2025-10-03 14:47:02 +02:00
parent 06e19bd7e6
commit 3a8fb0a5e5
6 changed files with 1 additions and 454 deletions

View File

@@ -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.

View File

@@ -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: {

View File

@@ -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'

View File

@@ -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 xcodejs 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;

View File

@@ -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: "",

View File

@@ -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;
};