import { fetch } from '@tauri-apps/plugin-http'; import { platform } from '@tauri-apps/plugin-os'; import { currentSettings } from './settings'; import { writable } from 'svelte/store'; import { get } from 'svelte/store'; /** * @typedef {Object} FileLink * @property {string} version - The version string (e.g., "5.0.0") * @property {string} downloadUrl - The full download URL * @property {string} platform - The platform identifier (e.g., 'linux', 'windows', 'macos') * @property {string} architecture - The architecture identifier (e.g., 'x64', 'arm64') * @property {string} fileExtension - The file extension (e.g., 'tar.xz', 'zip') * @property {string} status - The download status ('pending', 'downloading', 'completed', 'error') * @property {number} percent - The download progress percentage (0-100) */ /** * @typedef {Object} BlenderRelease * @property {string} original - The original href from the release page * @property {string} version - The version string (e.g., "2.80", "3.6", "4.2alpha") * @property {string} fullPath - The full URL to the release folder * @property {Date|null} lastModified - The last modified date (currently null) * @property {Array} links - Array of download links for this release */ export const blenderReleases = writable([]); export async function getBlenderReleases() { const settings = await get(currentSettings); const currentPlatform = settings.defaultPlatform || platform(); const currentArch = settings.defaultArch; try { // Fetch the HTML page const response = await fetch('https://download.blender.org/release/'); const html = await response.text(); // Parse the HTML const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); // Extract all links that look like version folders const links = doc.querySelectorAll('a'); /** @type {Promise[]} */ const folderPromises = []; links.forEach((link) => { const href = link.getAttribute('href'); // Filter for directory links (ending with '/') that are version folders // This regex matches patterns like Blender2.80/, Blender3.6/, Blender4.2alpha/, etc. if (href && href.endsWith('/') && href.match(/^Blender[\d.]+[a-z]*\//)) { // Remove the trailing slash and 'Blender' prefix for cleaner version names const versionName = href.slice(0, -1).replace('Blender', ''); // Remove trailing slash if (parseFloat(versionName) > 2.78) { const promise = getBlenderMinorVersionsWithDownload( `https://download.blender.org/release/${href}`, currentPlatform, currentArch ).then((links) => { // Only add to array if there are links if (links && links.length > 0) { return { original: href, version: versionName, // e.g., "2.80", "3.6", "4.2alpha" fullPath: `https://download.blender.org/release/${href}`, lastModified: null, // Date info is in a separate column, harder to parse links: links }; } return null; }); folderPromises.push(promise); } } }); const folders = (await Promise.all(folderPromises)).filter((folder) => folder !== null); folders.sort((a, b) => compareVersions(b.version, a.version)); blenderReleases.set(folders); return folders; } catch (error) { console.error('Error fetching Blender release list:', error); return []; } } /** * Fetches the minor versions (e.g., 5.0.0, 5.1.0) for a given major version folder URL, * along with their download URLs for the current platform and architecture. * @returns {Promise} List of minor versions with download URLs * @param {string} majorVersionFolderUrl * @param {string} currentPlatform * @param {string} currentArch */ async function getBlenderMinorVersionsWithDownload( majorVersionFolderUrl, currentPlatform, currentArch ) { try { // 1. Fetch the HTML of the version folder (e.g., Blender5.0/) const response = await fetch(majorVersionFolderUrl); const html = await response.text(); // 2. Parse the HTML const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); // 3. Map your platform/arch to the naming convention used in filenames /** @type {{ linux: string; windows: string; darwin: string; }} */ const platformMap = { linux: 'linux', windows: 'windows', darwin: 'macos' // macOS is called 'darwin' in some contexts, but files use 'macos' }; /** @type {{ x86_64: string; arm64: string; }} */ const archMap = { x86_64: 'x64', arm64: 'arm64' }; const targetPlatform = platformMap[/** @type {keyof platformMap} */ (currentPlatform)] || currentPlatform; const targetArch = archMap[/** @type {keyof archMap} */ (currentArch)] || currentArch; // 4. Get all file links and process them const links = doc.querySelectorAll('a'); /** @type {Map} */ const versionsMap = new Map(); // Key: version string, Value: object with files links.forEach((link) => { const href = link.getAttribute('href'); if (!href) return; // Match pattern: blender-5.0.0-linux-x64.tar.xz // Regex captures: version, platform, arch, extension const match = href.match(/^blender-([\d.]+)-([a-z]+)-([a-z0-9]+)\.([a-z.]+)$/); if (match) { const [_, version, platform, arch, ext] = /** @type {[string, string, string, string, string]} */ (match); // Ignore checksum files (.md5, .sha256) if you don't need them if (ext === 'md5' || ext === 'sha256') return; // Store file info grouped by version if (!versionsMap.has(version)) { versionsMap.set(version, {}); } const versionFiles = versionsMap.get(version); if (!versionFiles) return; const key = `${platform}-${arch}`; versionFiles[key] = { url: new URL(href, majorVersionFolderUrl).href, extension: ext, platform, arch }; } }); // 5. For each version, pick the correct download link based on target platform/arch const results = []; const targetKey = `${targetPlatform}-${targetArch}`; for (const [version, files] of versionsMap.entries()) { const fileInfo = files[targetKey]; if (fileInfo) { results.push({ version: version, downloadUrl: fileInfo.url, platform: targetPlatform, architecture: targetArch, fileExtension: fileInfo.extension, status: 'pending', percent: 0 }); } else { console.warn(`No matching file found for ${version} with ${targetKey}`); } } return results.sort((a, b) => compareVersions(a.version, b.version)); // Optional: sort versions } catch (error) { console.error('Error fetching Blender versions:', error); return []; } } // Helper function to compare version strings (e.g., "5.0.0", "5.0.1") /** * @param {string} v1 * @param {string} v2 */ function compareVersions(v1, v2) { const parts1 = v1.split('.').map(Number); const parts2 = v2.split('.').map(Number); for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) { const num1 = parts1[i] || 0; const num2 = parts2[i] || 0; if (num1 !== num2) return num1 - num2; } return 0; }