# VPS Integration - Code Changes Reference ## File: VPSAPIClient.swift (NEW FILE) Created a complete API client with these sections: 1. **Configuration & Initialization** - Singleton pattern - URLSession setup with appropriate timeouts - Base URL configuration 2. **Health Check** ```swift func checkHealth() async throws -> Bool ``` 3. **Download Operations** ```swift func downloadChapter(...) async throws -> VPSDownloadResult ``` - Progress tracking via `@Published` properties - Active download tracking 4. **Status Checking** ```swift func getChapterManifest(...) async throws -> VPSChapterManifest? func listDownloadedChapters(...) async throws -> [VPSChapterInfo] ``` 5. **Image URLs** ```swift func getImageURL(...) -> String ``` 6. **Management** ```swift func deleteChapter(...) async throws -> Bool func getStorageStats() async throws -> VPSStorageStats ``` --- ## File: MangaDetailView.swift ### Change 1: Import VPS Client ```swift // Added to MangaDetailView struct @StateObject private var vpsClient = VPSAPIClient.shared ``` ### Change 2: Add VPS Download Button to Toolbar ```swift // In toolbar Button { viewModel.showingVPSDownloadAll = true } label: { Image(systemName: "icloud.and.arrow.down") } .disabled(viewModel.chapters.isEmpty) ``` ### Change 3: Add VPS Download Alert ```swift .alert("Descargar a VPS", isPresented: $viewModel.showingVPSDownloadAll) { Button("Cancelar", role: .cancel) { } Button("Últimos 10 a VPS") { Task { await viewModel.downloadLastChaptersToVPS(count: 10) } } Button("Todos a VPS") { Task { await viewModel.downloadAllChaptersToVPS() } } } message: { Text("¿Cuántos capítulos quieres descargar al servidor VPS?") } ``` ### Change 4: Update ChapterRowView ```swift struct ChapterRowView: View { // Added: let onVPSDownloadToggle: () async -> Void @ObservedObject var vpsClient = VPSAPIClient.shared @State private var isVPSDownloaded = false @State private var isVPSChecked = false ``` ### Change 5: Add VPS Status Indicator in ChapterRowView Body ```swift // VPS Download Button / Status if isVPSChecked { if isVPSDownloaded { Image(systemName: "icloud.fill") .foregroundColor(.blue) } else { Button { Task { await onVPSDownloadToggle() } } label: { Image(systemName: "icloud.and.arrow.up") .foregroundColor(.blue) } .buttonStyle(.plain) } } ``` ### Change 6: Add VPS Progress Display ```swift // Mostrar progreso de descarga VPS if vpsClient.activeDownloads.contains("\(mangaSlug)-\(chapter.number)"), let progress = vpsClient.downloadProgress["\(mangaSlug)-\(chapter.number)"] { HStack { Image(systemName: "icloud.and.arrow.down") .font(.caption2) .foregroundColor(.blue) ProgressView(value: progress) .progressViewStyle(.linear) .frame(maxWidth: 100) Text("VPS \(Int(progress * 100))%") .font(.caption2) .foregroundColor(.blue) } } ``` ### Change 7: Add VPS Status Check Function ```swift private func checkVPSStatus() async { do { let manifest = try await vpsClient.getChapterManifest( mangaSlug: mangaSlug, chapterNumber: chapter.number ) isVPSDownloaded = manifest != nil isVPSChecked = true } catch { isVPSDownloaded = false isVPSChecked = true } } ``` ### Change 8: Update chaptersList to Pass VPS Callback ```swift ChapterRowView( chapter: chapter, mangaSlug: manga.slug, onTap: { viewModel.selectedChapter = chapter }, onDownloadToggle: { await viewModel.downloadChapter(chapter) }, onVPSDownloadToggle: { // NEW await viewModel.downloadChapterToVPS(chapter) } ) ``` ### Change 9: ViewModel Additions ```swift // New Published Property @Published var showingVPSDownloadAll = false // New Dependency private let vpsClient = VPSAPIClient.shared // New Methods func downloadChapterToVPS(_ chapter: Chapter) async { do { let imageUrls = try await scraper.scrapeChapterImages(chapterSlug: chapter.slug) let result = try await vpsClient.downloadChapter( mangaSlug: manga.slug, chapterNumber: chapter.number, chapterSlug: chapter.slug, imageUrls: imageUrls ) // Handle result and show notification } catch { // Handle error } } func downloadAllChaptersToVPS() async { /* ... */ } func downloadLastChaptersToVPS(count: Int) async { /* ... */ } ``` --- ## File: ReaderView.swift ### Change 1: Add VPS Client ```swift // Added to ReaderView struct @ObservedObject private var vpsClient = VPSAPIClient.shared ``` ### Change 2: Update PageView for VPS Support ```swift struct PageView: View { // Added: @ObservedObject var vpsClient = VPSAPIClient.shared @State private var useVPS = false var body: some View { // ... if let localURL = StorageService.shared.getImageURL(...) { // Load from local cache } else if useVPS { // Load from VPS let vpsImageURL = vpsClient.getImageURL( mangaSlug: mangaSlug, chapterNumber: chapterNumber, pageIndex: page.index + 1 ) AsyncImage(url: URL(string: vpsImageURL)) { /* ... */ } } else { // Load from original URL fallbackImage } // ... .task { // Check if VPS has this chapter if let manifest = try? await vpsClient.getChapterManifest(...) { useVPS = true } } } private var fallbackImage: some View { /* ... */ } } ``` ### Change 3: Update ReaderViewModel ```swift // New Published Property @Published var isVPSDownloaded = false // New Dependency private let vpsClient = VPSAPIClient.shared // Updated loadPages() func loadPages() async { // 1. Check VPS first if let vpsManifest = try await vpsClient.getChapterManifest(...) { // Load from VPS isVPSDownloaded = true } // 2. Then local storage else if let downloadedChapter = storage.getDownloadedChapter(...) { // Load from local isDownloaded = true } // 3. Finally scrape else { // Scrape from original } } ``` ### Change 4: Update Reader Footer ```swift // Page indicator section if viewModel.isVPSDownloaded { Label("VPS", systemImage: "icloud.fill") .font(.caption) .foregroundColor(.blue) } if viewModel.isDownloaded { Label("Local", systemImage: "checkmark.circle.fill") .font(.caption) .foregroundColor(.green) } ``` --- ## Data Models (in VPSAPIClient.swift) ### VPSDownloadResult ```swift struct VPSDownloadResult { let success: Bool let alreadyDownloaded: Bool let manifest: VPSChapterManifest? let downloaded: Int? let failed: Int? } ``` ### VPSChapterManifest ```swift struct VPSChapterManifest: Codable { let mangaSlug: String let chapterNumber: Int let totalPages: Int let downloadedPages: Int let failedPages: Int let downloadDate: String let totalSize: Int let images: [VPSImageInfo] } ``` ### VPSChapterInfo ```swift struct VPSChapterInfo: Codable { let chapterNumber: Int let downloadDate: String let totalPages: Int let downloadedPages: Int let totalSize: Int let totalSizeMB: String } ``` ### VPSStorageStats ```swift struct VPSStorageStats: Codable { let totalMangas: Int let totalChapters: Int let totalSize: Int let totalSizeMB: String let totalSizeFormatted: String let mangaDetails: [VPSMangaDetail] } ``` --- ## Priority Order for Image Loading 1. **Local Device Storage** (fastest, offline) 2. **VPS Storage** (fast, online) 3. **Original URL** (slowest, may fail) This ensures best performance and reliability. --- ## Error Handling Pattern All VPS operations follow this pattern: ```swift do { let result = try await vpsClient.someMethod(...) // Handle success } catch { // Handle error - show user notification notificationMessage = "Error: \(error.localizedDescription)" showDownloadNotification = true } ``` --- ## Progress Tracking Pattern For downloads: ```swift // Track active downloads vpsClient.activeDownloads.contains("downloadId") // Get progress vpsClient.downloadProgress["downloadId"] // Display in UI ProgressView(value: progress) Text("\(Int(progress * 100))%") ```