app-ipc-methods.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. /**
  2. * @fileoverview Enhanced IPC methods for GrabZilla app
  3. * @author GrabZilla Development Team
  4. * @version 2.1.0
  5. * @since 2024-01-01
  6. */
  7. /**
  8. * Enhanced IPC methods to replace placeholder implementations in app.js
  9. * These methods provide full Electron IPC integration for desktop functionality
  10. */
  11. /**
  12. * Enhanced cookie file selection with IPC integration
  13. * Replaces the placeholder handleSelectCookieFile method
  14. */
  15. async function handleSelectCookieFile() {
  16. if (!window.electronAPI) {
  17. this.showStatus('File selection not available in browser mode', 'error');
  18. return;
  19. }
  20. try {
  21. this.showStatus('Opening file dialog...', 'info');
  22. const cookieFilePath = await window.electronAPI.selectCookieFile();
  23. if (cookieFilePath) {
  24. // Update configuration with selected cookie file
  25. this.state.updateConfig({ cookieFile: cookieFilePath });
  26. // Update UI to show selected file
  27. this.updateCookieFileUI(cookieFilePath);
  28. this.showStatus('Cookie file selected successfully', 'success');
  29. console.log('Cookie file selected:', cookieFilePath);
  30. } else {
  31. this.showStatus('Cookie file selection cancelled', 'info');
  32. }
  33. } catch (error) {
  34. console.error('Error selecting cookie file:', error);
  35. this.showStatus('Failed to select cookie file', 'error');
  36. }
  37. }
  38. /**
  39. * Enhanced save directory selection with IPC integration
  40. */
  41. async function handleSelectSaveDirectory() {
  42. if (!window.electronAPI) {
  43. this.showStatus('Directory selection not available in browser mode', 'error');
  44. return;
  45. }
  46. try {
  47. this.showStatus('Opening directory dialog...', 'info');
  48. const directoryPath = await window.electronAPI.selectSaveDirectory();
  49. if (directoryPath) {
  50. // Update configuration with selected directory
  51. this.state.updateConfig({ savePath: directoryPath });
  52. // Update UI to show selected directory
  53. this.updateSavePathUI(directoryPath);
  54. this.showStatus('Save directory selected successfully', 'success');
  55. console.log('Save directory selected:', directoryPath);
  56. } else {
  57. this.showStatus('Directory selection cancelled', 'info');
  58. }
  59. } catch (error) {
  60. console.error('Error selecting save directory:', error);
  61. this.showStatus('Failed to select save directory', 'error');
  62. }
  63. }
  64. /**
  65. * Enhanced video download with full IPC integration
  66. * Replaces the placeholder handleDownloadVideos method
  67. */
  68. async function handleDownloadVideos() {
  69. const readyVideos = this.state.getVideosByStatus('ready');
  70. if (readyVideos.length === 0) {
  71. this.showStatus('No videos ready for download', 'info');
  72. return;
  73. }
  74. if (!window.electronAPI) {
  75. this.showStatus('Video download not available in browser mode', 'error');
  76. return;
  77. }
  78. // Check if save path is configured
  79. if (!this.state.config.savePath) {
  80. this.showStatus('Please select a save directory first', 'error');
  81. return;
  82. }
  83. try {
  84. // Set downloading state
  85. this.state.updateUI({ isDownloading: true });
  86. this.updateControlPanelState();
  87. // Set up progress listener for this download session
  88. const progressListenerId = 'download-session-' + Date.now();
  89. window.IPCManager.onDownloadProgress(progressListenerId, (progressData) => {
  90. this.handleDownloadProgress(progressData);
  91. });
  92. this.showStatus(`Starting download of ${readyVideos.length} video(s)...`, 'info');
  93. // Download videos sequentially to avoid overwhelming the system
  94. for (const video of readyVideos) {
  95. try {
  96. // Update video status to downloading
  97. this.state.updateVideo(video.id, {
  98. status: 'downloading',
  99. progress: 0
  100. });
  101. this.renderVideoList();
  102. // Prepare download options
  103. const downloadOptions = {
  104. url: video.url,
  105. quality: video.quality,
  106. format: video.format,
  107. savePath: this.state.config.savePath,
  108. cookieFile: this.state.config.cookieFile
  109. };
  110. // Start download
  111. const result = await window.electronAPI.downloadVideo(downloadOptions);
  112. if (result.success) {
  113. // Update video status to completed
  114. this.state.updateVideo(video.id, {
  115. status: 'completed',
  116. progress: 100,
  117. filename: result.filename || 'Downloaded'
  118. });
  119. console.log(`Successfully downloaded: ${video.title}`);
  120. } else {
  121. throw new Error(result.error || 'Download failed');
  122. }
  123. } catch (error) {
  124. console.error(`Failed to download video ${video.id}:`, error);
  125. // Update video status to error
  126. this.state.updateVideo(video.id, {
  127. status: 'error',
  128. error: error.message,
  129. progress: 0
  130. });
  131. }
  132. // Update UI after each video
  133. this.renderVideoList();
  134. }
  135. // Clean up progress listener
  136. window.IPCManager.removeDownloadProgressListener(progressListenerId);
  137. // Update final state
  138. this.state.updateUI({ isDownloading: false });
  139. this.updateControlPanelState();
  140. const completedCount = this.state.getVideosByStatus('completed').length;
  141. const errorCount = this.state.getVideosByStatus('error').length;
  142. if (errorCount === 0) {
  143. this.showStatus(`Successfully downloaded ${completedCount} video(s)`, 'success');
  144. } else {
  145. this.showStatus(`Downloaded ${completedCount} video(s), ${errorCount} failed`, 'warning');
  146. }
  147. } catch (error) {
  148. console.error('Error in download process:', error);
  149. this.showStatus(`Download process failed: ${error.message}`, 'error');
  150. // Reset state on error
  151. this.state.updateUI({ isDownloading: false });
  152. this.updateControlPanelState();
  153. }
  154. }
  155. /**
  156. * Enhanced metadata fetching with IPC integration
  157. * Replaces the placeholder fetchVideoMetadata method
  158. */
  159. async function fetchVideoMetadata(videoId, url) {
  160. try {
  161. // Update video status to indicate metadata loading
  162. this.state.updateVideo(videoId, {
  163. title: 'Loading metadata...',
  164. status: 'ready'
  165. });
  166. // Extract thumbnail immediately (this is fast)
  167. const thumbnail = await URLValidator.extractThumbnail(url);
  168. // Update video with thumbnail first
  169. if (thumbnail) {
  170. this.state.updateVideo(videoId, { thumbnail });
  171. this.renderVideoList();
  172. }
  173. // Fetch real metadata using Electron IPC if available
  174. let metadata;
  175. if (window.electronAPI) {
  176. try {
  177. metadata = await window.electronAPI.getVideoMetadata(url);
  178. } catch (error) {
  179. console.warn('Failed to fetch real metadata, using fallback:', error);
  180. metadata = await this.simulateMetadataFetch(url);
  181. }
  182. } else {
  183. // Fallback to simulation in browser mode
  184. metadata = await this.simulateMetadataFetch(url);
  185. }
  186. // Update video with fetched metadata
  187. if (metadata) {
  188. const updateData = {
  189. title: metadata.title || 'Unknown Title',
  190. duration: metadata.duration || '00:00',
  191. status: 'ready'
  192. };
  193. // Use fetched thumbnail if available, otherwise keep the one we extracted
  194. if (metadata.thumbnail) {
  195. updateData.thumbnail = metadata.thumbnail;
  196. }
  197. this.state.updateVideo(videoId, updateData);
  198. this.renderVideoList();
  199. console.log(`Metadata fetched for video ${videoId}:`, metadata);
  200. }
  201. } catch (error) {
  202. console.error(`Failed to fetch metadata for video ${videoId}:`, error);
  203. // Update video with error state but keep it downloadable
  204. this.state.updateVideo(videoId, {
  205. title: 'Metadata unavailable',
  206. status: 'ready',
  207. error: null // Clear any previous errors since this is just metadata
  208. });
  209. this.renderVideoList();
  210. }
  211. }
  212. /**
  213. * Enhanced binary checking with detailed status reporting
  214. * Replaces the placeholder checkBinaries method
  215. */
  216. async function checkBinaries() {
  217. if (!window.electronAPI) {
  218. console.warn('Electron API not available - running in browser mode');
  219. return;
  220. }
  221. try {
  222. console.log('Checking yt-dlp and ffmpeg binaries...');
  223. const binaryVersions = await window.electronAPI.checkBinaryVersions();
  224. // Update UI based on binary availability
  225. this.updateBinaryStatus(binaryVersions);
  226. if (binaryVersions.ytDlp.available && binaryVersions.ffmpeg.available) {
  227. console.log('All required binaries are available');
  228. console.log('yt-dlp version:', binaryVersions.ytDlp.version);
  229. console.log('ffmpeg version:', binaryVersions.ffmpeg.version);
  230. this.showStatus('All dependencies ready', 'success');
  231. } else {
  232. const missing = [];
  233. if (!binaryVersions.ytDlp.available) missing.push('yt-dlp');
  234. if (!binaryVersions.ffmpeg.available) missing.push('ffmpeg');
  235. console.warn('Missing binaries:', missing);
  236. this.showStatus(`Missing dependencies: ${missing.join(', ')}`, 'error');
  237. }
  238. } catch (error) {
  239. console.error('Error checking binaries:', error);
  240. this.showStatus('Failed to check dependencies', 'error');
  241. }
  242. }
  243. /**
  244. * Update cookie file UI to show selected file
  245. */
  246. function updateCookieFileUI(cookieFilePath) {
  247. const cookieFileBtn = document.getElementById('cookieFileBtn');
  248. if (cookieFileBtn) {
  249. // Update button text to show file is selected
  250. const fileName = cookieFilePath.split('/').pop() || cookieFilePath.split('\\').pop();
  251. cookieFileBtn.textContent = `Cookie File: ${fileName}`;
  252. cookieFileBtn.title = cookieFilePath;
  253. cookieFileBtn.classList.add('selected');
  254. }
  255. }
  256. /**
  257. * Update save path UI to show selected directory
  258. */
  259. function updateSavePathUI(directoryPath) {
  260. const savePath = document.getElementById('savePath');
  261. if (savePath) {
  262. savePath.textContent = directoryPath;
  263. savePath.title = directoryPath;
  264. }
  265. }
  266. /**
  267. * Update binary status UI based on version check results
  268. */
  269. function updateBinaryStatus(binaryVersions) {
  270. // Update UI elements to show binary status
  271. console.log('Binary status updated:', binaryVersions);
  272. // Store binary status in state for reference
  273. this.state.binaryStatus = binaryVersions;
  274. // Update dependency status indicators if they exist
  275. const ytDlpStatus = document.getElementById('ytdlp-status');
  276. if (ytDlpStatus) {
  277. ytDlpStatus.textContent = binaryVersions.ytDlp.available
  278. ? `yt-dlp ${binaryVersions.ytDlp.version}`
  279. : 'yt-dlp missing';
  280. ytDlpStatus.className = binaryVersions.ytDlp.available ? 'status-ok' : 'status-error';
  281. }
  282. const ffmpegStatus = document.getElementById('ffmpeg-status');
  283. if (ffmpegStatus) {
  284. ffmpegStatus.textContent = binaryVersions.ffmpeg.available
  285. ? `ffmpeg ${binaryVersions.ffmpeg.version}`
  286. : 'ffmpeg missing';
  287. ffmpegStatus.className = binaryVersions.ffmpeg.available ? 'status-ok' : 'status-error';
  288. }
  289. }
  290. /**
  291. * Handle download progress updates from IPC
  292. */
  293. function handleDownloadProgress(progressData) {
  294. const { url, progress } = progressData;
  295. // Find video by URL and update progress
  296. const video = this.state.videos.find(v => v.url === url);
  297. if (video) {
  298. this.state.updateVideo(video.id, { progress });
  299. this.renderVideoList();
  300. }
  301. }
  302. // Export methods for integration into main app
  303. if (typeof module !== 'undefined' && module.exports) {
  304. module.exports = {
  305. handleSelectCookieFile,
  306. handleSelectSaveDirectory,
  307. handleDownloadVideos,
  308. fetchVideoMetadata,
  309. checkBinaries,
  310. updateCookieFileUI,
  311. updateSavePathUI,
  312. updateBinaryStatus,
  313. handleDownloadProgress
  314. };
  315. } else if (typeof window !== 'undefined') {
  316. // Make methods available globally for integration
  317. window.EnhancedIPCMethods = {
  318. handleSelectCookieFile,
  319. handleSelectSaveDirectory,
  320. handleDownloadVideos,
  321. fetchVideoMetadata,
  322. checkBinaries,
  323. updateCookieFileUI,
  324. updateSavePathUI,
  325. updateBinaryStatus,
  326. handleDownloadProgress
  327. };
  328. }