import Common import Foundation
final class ScreenshotDownloader {
private let outputURL: URL private let token: String private let projectId: String private let api: Api private let cacheURL: URL init(figmaApi: Api, outputURL: URL, token: String, projectId: String) { self.outputURL = outputURL self.token = token self.projectId = projectId self.api = figmaApi self.cacheURL = self.outputURL.appendingPathComponent("screenshots_cache") let fm = FileManager.default try? fm.createDirectory(at: self.cacheURL, withIntermediateDirectories: true, attributes: [:]) } private func downloadIds(_ ids: [String], repeatCount: Int = 5, scale: Int) -> Images? { if repeatCount < 0 { return nil } do { let images = try self.api.images( token: self.token, projectId: self.projectId, ids: ids, scale: scale ) if let err = images.err { print("⛔️ Download error \(repeatCount - 1), try one more time: \(err)") return self.downloadIds(ids, repeatCount: repeatCount - 1, scale: scale) } else { return images } } catch { print("⛔️ Download batch error \(repeatCount - 1), try one more time after 15 sec: \(error.locd)") Thread.sleep(forTimeInterval: 15) return self.downloadIds(ids, repeatCount: repeatCount - 1, scale: scale) } } func download(ids: [String], scale: Int) -> [Images] { let downloadIDs = ids.unique let batch = 10 var allImages = [Images]() for idx in stride(from: downloadIDs.indices.lowerBound, to: downloadIDs.indices.upperBound, by: batch) { print("⬇️ Fetching image batch: \(idx)") let subsequence = downloadIDs[idx..<min(idx.advanced(by: batch), downloadIDs.count)] if let images = self.downloadIds(Array(subsequence), repeatCount: 6, scale: scale) { allImages.append(images) self.downloadImages(images) } else { print("💥 Download batch error, maybe we should limit requests other way") exit(1) } } return allImages } private func downloadImages(_ images: Images) { DispatchQueue.global().async { if let images = images.images { _ = DownloadBatch(images: images, url: self.cacheURL).download() } } } func download(screens: [Figma.Screen]) throws { var imageIDs2x = [String]() var imageIDs3x = [String]() for screen in screens { switch screen.device.scale { case 2: imageIDs2x.append(screen.id) case 3: imageIDs3x.append(screen.id) default: fatalError("🚨Unknown scale \(screen.device.scale)") } } var allImages = [Images]() allImages += self.download(ids: imageIDs2x, scale: 2) allImages += self.download(ids: imageIDs3x, scale: 3) var allImagesKeys = [String: String]() allImages .compactMap { $0.images } .filter { !$0.isEmpty } .forEach { (images) in for image in images { allImagesKeys[image.key] = image.value } } let imageData = DownloadBatch(images: allImagesKeys, url: self.cacheURL).download() let screenshotsURL = self.outputURL.appendingPathComponent("screenshots") let fm = FileManager.default do { try fm.removeItem(at: screenshotsURL) } catch { print("Remove screenshots error: \(error)") } try fm.createDirectory(at: screenshotsURL, withIntermediateDirectories: true, attributes: [:]) print("ℹ️ Process screenshots at \(screenshotsURL)") for screen in screens { let localeURL = screenshotsURL.localeURL(for: screen) do { try fm.createDirectory(at: localeURL, withIntermediateDirectories: true, attributes: [:]) if let data = imageData[screen.id] { print("ℹ️ Save screenshot \(localeURL.lastPathComponent)/\(screen.fileName)") do { try data.write(to: localeURL.appendingPathComponent(screen.fileName)) } catch { print("⛔️ Save screenshot error: \(error.locd)") } } } catch { print("⛔️ Create locale folder error: \(error.locd)") } } }
}
extension URL {
func localeURL(for screen: Figma.Screen) -> URL { var url = self if screen.device.isIMessage { // скриншоты для iMessage должны лежать в папке iMessage/Locale/###.jpg url.appendPathComponent("iMessage") } url.appendPathComponent(screen.locale) return url }
}
extension String {
var cacheName: String { "\(self.MD5String).jpg" }
}