Update: Setup file processing is now supported, but Intel chips are not yet supported.

This commit is contained in:
X1a0He
2024-11-05 20:30:18 +08:00
parent 7636b3534b
commit 0820135338
106 changed files with 1626 additions and 2162 deletions

View File

@@ -7,7 +7,7 @@
objects = {
/* Begin PBXFileReference section */
3CCC3AE02CC67B8F006E22B4 /* Adobe-Downloader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Adobe-Downloader.app"; sourceTree = BUILT_PRODUCTS_DIR; };
3CCC3AE02CC67B8F006E22B4 /* Adobe Downloader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Adobe Downloader.app"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
@@ -46,7 +46,7 @@
3CCC3AE12CC67B8F006E22B4 /* Products */ = {
isa = PBXGroup;
children = (
3CCC3AE02CC67B8F006E22B4 /* Adobe-Downloader.app */,
3CCC3AE02CC67B8F006E22B4 /* Adobe Downloader.app */,
);
name = Products;
sourceTree = "<group>";
@@ -54,9 +54,9 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
3CCC3ADF2CC67B8F006E22B4 /* Adobe-Downloader */ = {
3CCC3ADF2CC67B8F006E22B4 /* Adobe Downloader */ = {
isa = PBXNativeTarget;
buildConfigurationList = 3CCC3B052CC67B91006E22B4 /* Build configuration list for PBXNativeTarget "Adobe-Downloader" */;
buildConfigurationList = 3CCC3B052CC67B91006E22B4 /* Build configuration list for PBXNativeTarget "Adobe Downloader" */;
buildPhases = (
3CCC3ADC2CC67B8F006E22B4 /* Sources */,
3CCC3ADD2CC67B8F006E22B4 /* Frameworks */,
@@ -70,11 +70,11 @@
3CCC3AE22CC67B8F006E22B4 /* Adobe Downloader */,
3CCC3B112CC67F7A006E22B4 /* Localizables */,
);
name = "Adobe-Downloader";
name = "Adobe Downloader";
packageProductDependencies = (
);
productName = "Adobe-Downloader";
productReference = 3CCC3AE02CC67B8F006E22B4 /* Adobe-Downloader.app */;
productReference = 3CCC3AE02CC67B8F006E22B4 /* Adobe Downloader.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
@@ -106,7 +106,7 @@
projectDirPath = "";
projectRoot = "";
targets = (
3CCC3ADF2CC67B8F006E22B4 /* Adobe-Downloader */,
3CCC3ADF2CC67B8F006E22B4 /* Adobe Downloader */,
);
};
/* End PBXProject section */
@@ -326,7 +326,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
3CCC3B052CC67B91006E22B4 /* Build configuration list for PBXNativeTarget "Adobe-Downloader" */ = {
3CCC3B052CC67B91006E22B4 /* Build configuration list for PBXNativeTarget "Adobe Downloader" */ = {
isa = XCConfigurationList;
buildConfigurations = (
3CCC3B062CC67B91006E22B4 /* Debug */,

View File

@@ -3,6 +3,7 @@ import SwiftUI
@main
struct Adobe_DownloaderApp: App {
@StateObject private var networkManager = NetworkManager()
@State private var showBackupAlert = false
init() {
if UserDefaults.standard.object(forKey: "useDefaultLanguage") == nil {
@@ -17,7 +18,25 @@ struct Adobe_DownloaderApp: App {
WindowGroup {
ContentView()
.environmentObject(networkManager)
.frame(width: 850, height: 700)
.frame(width: 850, height: 800)
.tint(.blue)
.onAppear {
if ModifySetup.checkSetupBackup() {
showBackupAlert = true
}
}
.alert("Setup未备份提示", isPresented: $showBackupAlert) {
Button("OK") {
ModifySetup.backupSetupFile { success, message in
if !success {
print(message)
}
}
}
Button("取消", role: .cancel) {}
} message: {
Text("检测到Setup文件尚未备份如果你需要安装程序则Setup必须被处理点击确定后你需要输入密码Adobe Downloader将自动处理并备份为Setup.original")
}
}
.windowStyle(.hiddenTitleBar)
.windowResizability(.contentSize)

View File

@@ -8,4 +8,4 @@
"author" : "xcode",
"version" : 1
}
}
}

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "acrobat-pro.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="256px" height="256px" viewBox="0 0 256 256" style="enable-background:new 0 0 256 256;" xml:space="preserve">
<style type="text/css">
.st0{fill:#B30B00;}
.st1{fill:#FFFFFF;}
</style>
<g>
<path class="st0" d="M45.5,3.2h165.1c24.7,0,45.5,19.9,45.5,45.5v158.7c0,24.7-19.9,45.5-45.5,45.5H45.5
C20.7,252.8,0,232.9,0,207.4V48.6C0,23.1,19.9,3.2,45.5,3.2z"/>
<path class="st1" d="M204.2,147.5c-12-12.8-44.7-7.2-52.6-6.4c-11.2-11.2-19.1-23.9-22.3-28.7c4-12,7.2-25.5,7.2-38.3
c0-12-4.8-23.9-17.5-23.9c-4.8,0-8.8,2.4-11.2,6.4c-5.6,9.6-3.2,28.7,5.6,48.6c-4.8,14.4-12.8,35.9-22.3,52.6
c-12.8,4.8-40.7,17.5-43.1,31.9c-0.8,4,0.8,8.8,4,11.2c3.2,3.2,7.2,4,11.2,4c16.7,0,33.5-23.1,45.5-43.9c9.6-3.2,24.7-8,39.9-10.4
c17.5,16,33.5,18.3,41.5,18.3c11.2,0,15.2-4.8,16.7-8.8C208.9,156.3,207.4,150.7,204.2,147.5z M193,155.5c-0.8,3.2-4.8,6.4-12,4.8
c-8.8-2.4-16.7-6.4-23.1-12c5.6-0.8,19.1-2.4,28.7-0.8C189.8,148.3,193.8,150.7,193,155.5z M115.6,59.8c0.8-1.6,2.4-2.4,4-2.4
c4,0,4.8,4.8,4.8,8.8c-0.8,9.6-2.4,19.9-5.6,28.7C112.4,77.4,113.2,64.6,115.6,59.8z M114.8,149.9c4-7.2,8.8-20.7,10.4-25.5
c4,7.2,11.2,15.2,14.4,19.1C140.4,142.8,126,145.9,114.8,149.9z M87.7,168.3C76.6,185.8,66.2,197,59.8,197c-0.8,0-2.4,0-3.2-0.8
c-0.8-1.6-1.6-3.2-0.8-4.8C56.6,185,69.4,176.2,87.7,168.3z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "192x192.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "after-effects.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 240 234" style="enable-background:new 0 0 240 234;" xml:space="preserve">
<style type="text/css">
.st0{fill:#00005B;}
.st1{fill:#9999FF;}
</style>
<g id="Layer_2_1_">
<g id="Surfaces">
<g id="Video_Audio_Surface">
<g id="Outline_no_shadow">
<path class="st0" d="M42.5,0h155C221,0,240,19,240,42.5v149c0,23.5-19,42.5-42.5,42.5h-155C19,234,0,215,0,191.5v-149
C0,19,19,0,42.5,0z"/>
</g>
</g>
</g>
<g id="Outlined_Mnemonics_Logos">
<g id="Ae">
<path class="st1" d="M96.4,140H59.2l-7.6,23.6c-0.2,0.9-1,1.5-1.9,1.4H30.9c-1.1,0-1.4-0.6-1.1-1.8L62,70.9c0.3-1,0.6-1.9,1-3.1
c0.4-2.1,0.6-4.3,0.6-6.5c-0.1-0.5,0.3-1,0.8-1.1c0.1,0,0.2,0,0.3,0h25.6c0.7,0,1.2,0.3,1.3,0.8l36.5,102.5c0.3,1.1,0,1.6-1,1.6
h-20.9c-0.7,0.1-1.4-0.4-1.6-1.1L96.4,140z M65,120.1h25.4c-0.6-2.1-1.4-4.6-2.3-7.2c-0.9-2.7-1.8-5.6-2.7-8.6
c-1-3.1-1.9-6.1-2.9-9.2c-1-3.1-1.9-6-2.7-8.9c-0.8-2.8-1.5-5.4-2.2-7.8h-0.2c-0.9,4.3-2,8.6-3.4,12.9c-1.5,4.8-3,9.8-4.6,14.8
C68.1,111.2,66.5,115.9,65,120.1z"/>
<path class="st1" d="M187.4,130.8h-31.7c0.4,3.1,1.4,6.2,3.1,8.9c1.8,2.7,4.3,4.8,7.3,6c4,1.7,8.4,2.6,12.8,2.5
c3.5-0.1,7-0.4,10.4-1.1c3.1-0.4,6.1-1.2,8.9-2.3c0.5-0.4,0.8-0.2,0.8,0.8v15.3c0,0.4-0.1,0.8-0.2,1.2c-0.2,0.3-0.4,0.5-0.7,0.7
c-3.2,1.4-6.5,2.4-10,3c-4.7,0.9-9.4,1.3-14.2,1.2c-7.6,0-14-1.2-19.2-3.5c-4.9-2.1-9.2-5.4-12.6-9.5c-3.2-3.9-5.5-8.3-6.9-13.1
c-1.4-4.7-2.1-9.6-2.1-14.6c0-5.4,0.8-10.7,2.5-15.9c1.6-5,4.1-9.6,7.5-13.7c3.3-4,7.4-7.2,12.1-9.5c4.7-2.3,10.3-3.1,16.7-3.1
c5.3-0.1,10.6,0.9,15.5,3.1c4.1,1.8,7.7,4.5,10.5,8c2.6,3.4,4.7,7.2,6,11.4c1.3,4,1.9,8.1,1.9,12.2c0,2.4-0.1,4.5-0.2,6.4
c-0.2,1.9-0.3,3.3-0.4,4.2c-0.1,0.7-0.7,1.3-1.4,1.3c-0.6,0-1.7,0.1-3.3,0.2c-1.6,0.2-3.5,0.3-5.8,0.3
C192.4,131.2,190,130.8,187.4,130.8z M155.7,116.2h21.1c2.6,0,4.5,0,5.7-0.1c0.8-0.1,1.6-0.3,2.3-0.8v-1c0-1.3-0.2-2.5-0.6-3.7
c-1.8-5.6-7.1-9.4-13-9.2c-5.5-0.3-10.7,2.6-13.3,7.6C156.7,111.3,156,113.7,155.7,116.2z"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "animate.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#00005b;}.cls-2{fill:#99f;}</style></defs><title>Asset 148</title><g id="Layer_2" data-name="Layer 2"><g id="Surfaces"><g id="Video_Audio_Surface" data-name="Video/Audio Surface"><g id="Outline_no_shadow" data-name="Outline no shadow"><rect class="cls-1" width="240" height="234" rx="42.5"/></g></g></g><g id="Outlined_Mnemonics_Logos" data-name="Outlined Mnemonics &amp; Logos"><g id="An"><path class="cls-2" d="M97.04315,140H59.85223l-7.6377,23.46769a1.89429,1.89429,0,0,1-1.93212,1.449H31.44549q-1.61133,0-1.127-1.771L62.65236,70.87622c.32227-.96582.644-1.82674.96631-3.06209a34.47808,34.47808,0,0,0,.644-6.52027.99642.99642,0,0,1,1.127-1.12719H90.9883q1.125,0,1.28808.80517l36.41325,102.33472q.48267,1.61133-.96631,1.61011H106.79315a1.48825,1.48825,0,0,1-1.60987-1.127ZM65.71144,120.14233h25.438q-.96606-3.21863-2.25391-7.24511-1.29053-4.02246-2.73681-8.61353-1.44946-4.58826-2.89844-9.177-1.44873-4.58826-2.65625-8.855-1.20777-4.26379-2.17334-7.80835h-.16113a130.10721,130.10721,0,0,1-3.38086,12.87988q-2.2566,7.24512-4.58887,14.812Q67.96413,113.70484,65.71144,120.14233Z"/><path class="cls-2" d="M139.95981,163.38436V104.04224q0-2.7356-.08007-6.11792-.0835-3.38086-.2417-6.43994c-.1084-2.03785-.21631-3.93059-.32178-5.113a.9562.9562,0,0,1,.16064-.96607,1.31358,1.31358,0,0,1,.96631-.322h16.07145a3.59919,3.59919,0,0,1,1.61036.322,1.51637,1.51637,0,0,1,.80468,1.127q.32081.96606.72461,2.17358a16.52607,16.52607,0,0,1,.56348,3.2614,32.34487,32.34487,0,0,1,10.626-6.19849A36.57379,36.57379,0,0,1,182.919,83.75635a30.62737,30.62737,0,0,1,9.41846,1.52954,23.79268,23.79268,0,0,1,15.21435,14.812c1.60987,4.13306,2.24138,9.47363,2.24138,16.01929v47.26717q0,1.44909-1.28809,1.449L189.6924,165a1.42233,1.42233,0,0,1-1.60986-1.61011V118.544a22.1973,22.1973,0,0,0-1.94385-8.70557,11.50377,11.50377,0,0,0-4.10547-4.991,12.1157,12.1157,0,0,0-6.84229-1.771,20.031,20.031,0,0,0-7.56689,1.3684,19.08763,19.08763,0,0,0-5.957,3.78345v55.15609q0,1.44909-1.28809,1.449H141.409A1.28093,1.28093,0,0,1,139.95981,163.38436Z"/></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,61 +1,61 @@
{
"images" : [
{
"filename" : "16.png",
"filename" : "16pt-1.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "32 2x.png",
"filename" : "32pt-1.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "32 1x.png",
"filename" : "32pt-2.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "64 1.png",
"filename" : "64pt-1.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "128.png",
"filename" : "128pt-1.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "256 2x.png",
"filename" : "256pt-1.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "256 1x.png",
"filename" : "256pt-2.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "512 2x.png",
"filename" : "512pt-1.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "Adobe Downloader- 512 x 513.png",
"filename" : "512pt-2.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "Adobe Downloader - 1024 x 1025.png",
"filename" : "1024pt-1.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "192x192 (1).png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "audition.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#00005b;}.cls-2{fill:#99f;}</style></defs><title>Artboard 1</title><rect class="cls-1" width="240" height="234" rx="42.5"/><path class="cls-2" d="M94.32324,140H57.13184l-7.5669,23.551A1.89358,1.89358,0,0,1,47.63281,165H28.7959q-1.61133,0-1.127-1.771L59.86914,70.87622q.48267-1.44873.96582-3.30054a29.74633,29.74633,0,0,0,.78573-6.37858.99662.99662,0,0,1,1.12695-1.12719H88.34676q1.12426,0,1.28809.80517L126.04,163.38989q.48267,1.61133-.96582,1.61011H104.144a1.48792,1.48792,0,0,1-1.60986-1.12695Zm-31.395-19.85767h25.438q-.96606-3.21863-2.25439-7.24511-1.29054-4.02246-2.73682-8.61353-1.44873-4.58826-2.89795-9.177-1.44873-4.58826-2.65674-8.855-1.207-4.26379-2.17334-7.80835h-.16113A129.83529,129.83529,0,0,1,72.105,91.32324q-2.25659,7.24512-4.58838,14.812Q65.18091,113.70484,62.92822,120.14233Z"/><path class="cls-2" d="M202.83691,86.21053v61.30168q0,4.02648.08057,7.16455.07909,3.13953.32178,5.47412c.16113,1.55713.29443,2.4895.40283,3.561.10547.86036-.269,1.28809-1.12695,1.28809h-17.227a1.8896,1.8896,0,0,1-1.93213-1.12695,24.25482,24.25482,0,0,1-.56348-2.49561,10.58926,10.58926,0,0,1-.2417-2.11206,28.73423,28.73423,0,0,1-11.27,6.19849,43.559,43.559,0,0,1-11.10888,1.52954,33.20536,33.20536,0,0,1-10.86719-1.69068,21.20047,21.20047,0,0,1-8.53321-5.47387,25.79177,25.79177,0,0,1-5.63476-9.66016,44.18714,44.18714,0,0,1-2.0127-14.24829v-49.549a1.1382,1.1382,0,0,1,1.28809-1.28809h19.13314a1.13916,1.13916,0,0,1,1.28808,1.28809v46.97282q0,6.60351,2.898,10.38452c1.93213,2.52319,5.82194,3.78345,10.32976,3.78345a18.26761,18.26761,0,0,0,6.76171-1.20752,25.49323,25.49323,0,0,0,6.34392-3.3003V86.21053c0-.74976.48291-1.1272,1.44922-1.1272H201.71A.9976.9976,0,0,1,202.83691,86.21053Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "192x192.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "bridge.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#000b1d;}.cls-2{fill:#fff;}</style></defs><title>Artboard 1</title><rect class="cls-1" width="240" height="234" rx="42.5"/><path class="cls-2" d="M55.0625,164.1665,55,62.03052q0-1.44873.96631-1.60987,2.73559-.15857,8.37158-.24145c3.75586-.053,7.4303-.10571,11.83264-.16113q6.59985-.07911,12.07471-.08057c8.7998,0,16.00476.63477,21.21228,2.56689q7.80688,2.89783,11.99463,7.32544a24.1801,24.1801,0,0,1,5.91113,9.25757,31.04394,31.04394,0,0,1,1.44922,8.855,25.61447,25.61447,0,0,1-1.88672,9.41846,25.135,25.135,0,0,1-3.86377,6.84253,18.37823,18.37823,0,0,1-4.34717,4.0249,32.122,32.122,0,0,1,6.35938,4.42749,26.24885,26.24885,0,0,1,6.0376,7.96948,25.958,25.958,0,0,1,2.77453,12.19861,28.992,28.992,0,0,1-5.59192,17.26453q-5.39574,7.407-15.45605,11.51147A63.4326,63.4326,0,0,1,88.769,165.9375H78.0625c-3.38086,0-6.231-.02759-9.07324-.08057q-4.26783-.08313-7.40625-.16089-3.13917-.08313-5.39356-.08056Q55.06153,165.45692,55.0625,164.1665ZM76.83264,78.87024l.10486,22.27405H87.481q4.34693,0,8.5332.16089a29.9335,29.9335,0,0,1,5.95654.644,14.59966,14.59966,0,0,0,3.46192-4.34693,12.49237,12.49237,0,0,0,1.63659-6.1715,12.28643,12.28643,0,0,0-2.3612-7.35242,12.52579,12.52579,0,0,0-6.0376-4.10547,32.23991,32.23991,0,0,0-10.16736-1.26379H83.83411c-1.396,0-2.252.02783-3.37891.08056Q78.76477,78.8728,76.83264,78.87024Zm.10486,41.06726v26.76978c1.82422.10815,3.24316.18872,5.06934.24145q2.7356.08313,6.60107.08057a42.59823,42.59823,0,0,0,11.67236-1.449,15.09155,15.09155,0,0,0,7.64746-4.508,11.7948,11.7948,0,0,0,3.13477-7.5669,13.99712,13.99712,0,0,0-2.65137-7.56714c-1.39648-2.14575-3.97217-3.90771-7.728-4.87378a39.35908,39.35908,0,0,0-4.42774-.80493,46.77008,46.77008,0,0,0-5.87646-.322Z"/><path class="cls-2" d="M148.17627,85.01489H165.7251a2.18992,2.18992,0,0,1,2.09277,1.61011,7.78189,7.78189,0,0,1,.80518,2.25391,26.02512,26.02512,0,0,1,.48291,3.13964c.10595,1.127.16113,2.6858.16113,3.97364a38.56036,38.56036,0,0,1,10.70654-8.9646c4.34717-2.415,8.93262-2.96509,14.51514-2.96509a1.28058,1.28058,0,0,1,1.44873,1.449v19.17481q0,1.1283-1.60986,1.127a36.87973,36.87973,0,0,0-10.168.72461,39.61157,39.61157,0,0,0-8.37207,2.6565,20.00151,20.00151,0,0,0-5.9126,3.70288V163.551q0,1.44909-1.28809,1.449H149.625a1.42233,1.42233,0,0,1-1.60986-1.61011V108.3894q0-3.53979-.08057-7.48657-.08277-3.94335-.2417-7.80859a57.31046,57.31046,0,0,0-.64355-6.95215.925.925,0,0,1,1.12695-1.1272Z"/></svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "192x192 (5).png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "192x192 (1).png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#00005b;}.cls-2{fill:#99f;}</style></defs><title>Artboard 1</title><rect class="cls-1" width="240" height="234" rx="42.5"/><path class="cls-2" d="M114.83333,145.2583v16.583a2.124,2.124,0,0,1-1.28759,2.2539,40.74266,40.74266,0,0,1-9.48275,2.17359,103.546,103.546,0,0,1-12.63867.72461,71.17638,71.17638,0,0,1-17.79053-2.17359,52.60115,52.60115,0,0,1-15.37549-6.60107,46.89238,46.89238,0,0,1-11.91357-10.86743,48.17125,48.17125,0,0,1-7.728-14.9729,63.083,63.083,0,0,1-2.73731-19.32007q0-16.25976,7.084-28.57764a48.83838,48.83838,0,0,1,20.125-19.15893,66.09514,66.09514,0,0,1,31.07324-7.23845,101.80561,101.80561,0,0,1,12.47754.64405,37.34879,37.34879,0,0,1,8.45215,1.93213,2.07222,2.07222,0,0,1,.80517,1.93188V80.69727c0,.96606-.37744,1.28808-1.12695.96606a35.60969,35.60969,0,0,0-9.61291-2.93152,73.87457,73.87457,0,0,0-11.75293-.8855,36.76712,36.76712,0,0,0-18.40125,4.46106A30.14377,30.14377,0,0,0,63.08838,94.30176a37.1591,37.1591,0,0,0-4.186,18.11255,39.19347,39.19347,0,0,0,2.0127,13.2019,31.3052,31.3052,0,0,0,5.55469,9.82105,27.51331,27.51331,0,0,0,8.0498,6.60107,39.61266,39.61266,0,0,0,9.57959,3.62256A43.452,43.452,0,0,0,94,147.16667a108.58423,108.58423,0,0,0,10.86768-.48316,37.49154,37.49154,0,0,0,8.67806-2.23014q.64086-.483.96582-.24146A1.28143,1.28143,0,0,1,114.83333,145.2583Z"/><path class="cls-2" d="M151.7998,107.26221v56.4497c0,.86036-.37744,1.28809-1.12695,1.28809H130.58333a1.28093,1.28093,0,0,1-1.44922-1.449V52.36133c0-.74976.42774-1.127,1.28809-1.127h20.25065a.99742.99742,0,0,1,1.12695,1.127l.11687,37.99609a46.019,46.019,0,0,1,9.30159-4.74951c3.38086-1.23267,7.3252-1.52458,11.8335-1.52458a32.15363,32.15363,0,0,1,9.41845,1.449,22.33874,22.33874,0,0,1,8.77442,4.74455c2.68164,2.415,4.5638,5.69018,6.17366,9.821q2.41481,6.19959,2.415,16.01929V163.551q0,1.44909-1.28808,1.449H179.6932a1.42233,1.42233,0,0,1-1.60987-1.61011V118.37134a20.19262,20.19262,0,0,0-1.52978-8.45264,11.9783,11.9783,0,0,0-4.186-5.07129,13.74062,13.74062,0,0,0-7.36605-1.771,23.842,23.842,0,0,0-7.084,1.04638A19.51536,19.51536,0,0,0,151.7998,107.26221Z"/></svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "Character Animator.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "dreamweaver.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#470137;}.cls-2{fill:#ff61f6;}</style></defs><title>Asset 108</title><g id="Layer_2" data-name="Layer 2"><g id="Surfaces"><g id="UI_UX_Surface" data-name="UI/UX Surface"><g id="Outline_no_shadow" data-name="Outline no shadow"><rect class="cls-1" width="240" height="234" rx="42.5"/></g></g></g><g id="Outlined_Mnemonics_Logos" data-name="Outlined Mnemonics &amp; Logos"><g id="Dw"><path class="cls-2" d="M22.79165,163.77319V61.3772a1.154,1.154,0,0,1,1.07061-1.28784q2.29406-.15858,6.72978-.24146,4.43435-.07947,10.40037-.16113,5.9651-.07911,12.69512-.08057,18.35382,0,30.43711,6.60108a43.38642,43.38642,0,0,1,18.12421,18.11254q6.04026,11.51257,6.04187,26.48438a61.904,61.904,0,0,1-3.13574,20.52759,48.50033,48.50033,0,0,1-8.56531,15.37549,51.60105,51.60105,0,0,1-12.31242,10.70654A52.6561,52.6561,0,0,1,69.594,163.69287a61.57744,61.57744,0,0,1-15.44771,2.01245H42.75139q-6.04373,0-11.24182-.08056-5.20112-.08313-7.80039-.24146Q22.79142,165.3833,22.79165,163.77319Zm21.10689-84.364v66.65405q1.68105,0,3.0592.08057,1.37629.08313,2.82959.16089,1.45144.08313,3.28836.08056a39.105,39.105,0,0,0,13.61264-2.2539,27.65111,27.65111,0,0,0,10.47691-6.762A30.60459,30.60459,0,0,0,83.895,126.26025a44.13817,44.13817,0,0,0,2.37083-14.9729,39.56635,39.56635,0,0,0-2.29429-14.08764A26.24931,26.24931,0,0,0,66.91748,81.01929,43.24974,43.24974,0,0,0,53.68753,79.0874q-3.06153,0-5.12388.08032Q46.49851,79.25085,43.89854,79.40918Z"/><path class="cls-2" d="M197.8317,165.39883H179.11545a1.42488,1.42488,0,0,1-1.19792-.40259,4.85239,4.85239,0,0,1-.59851-1.20752q-2.84653-12.0747-4.79168-21.33227-1.94741-9.25635-3.21912-15.69751-1.27376-6.43762-2.09613-10.948-.82488-4.50549-1.27285-7.56714h-.1494q-1.49853,6.60353-2.77,12.79956-1.27443,6.19959-2.77,12.47754-1.49853,6.27906-3.369,13.84595-1.87385,7.56959-4.11779,16.42187c-.20117,1.07422-.74836,1.61011-1.647,1.61011H132.39975a2.25187,2.25187,0,0,1-1.42224-.322,2.7092,2.7092,0,0,1-.67389-1.12695l-19.46506-77.28q-.44889-1.28687,1.19792-1.28809h18.8661q1.34732,0,1.49717.96607,2.99367,13.20336,5.016,22.78149,2.02165,9.58082,3.1442,16.34155,1.12322,6.76209,1.8718,11.02857.74723,4.26746,1.19792,7.00341h.29926a41.10162,41.10162,0,0,1,.74881-4.66894q.59668-2.89819,1.49717-7.48657.89844-4.58862,2.246-10.86743,1.34732-6.27906,3.06927-14.57056,1.71991-8.29029,4.5669-18.91748a3.4155,3.4155,0,0,1,.37463-1.28809c.1494-.21386.524-.322,1.12254-.322h19.61492c.599,0,.94726.37744,1.04807,1.1272q2.54411,10.95043,4.26719,19.2395,1.71992,8.29395,2.9948,14.65088,1.27035,6.36217,1.94628,10.948.67366,4.58862,1.27285,7.728a48.99622,48.99622,0,0,1,.74881,5.07154h.29925q.74723-3.0564,1.34778-7.084.596-4.02246,1.57209-9.09668.972-5.07129,2.09614-11.18945,1.12321-6.11646,2.77-13.68481,1.64566-7.56592,3.74316-16.42212.29834-1.28687,1.34777-1.28809h17.51833q1.34732,0,1.04807,1.449l-21.5612,77.11914a3.5396,3.5396,0,0,1-.599,1.12695A1.6416,1.6416,0,0,1,197.8317,165.39883Z"/></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "192x192 (3).png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "192x192 (2).png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "illustrator.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#300;}.cls-2{fill:#ff9a00;}</style></defs><title>Asset 104</title><g id="Layer_2" data-name="Layer 2"><g id="Surfaces"><g id="Drawing_Surface" data-name="Drawing Surface"><g id="Outline_no_shadow" data-name="Outline no shadow"><rect class="cls-1" width="240" height="234" rx="42.5"/></g></g></g><g id="Outlined_Mnemonics_Logos" data-name="Outlined Mnemonics &amp; Logos"><g id="Ai"><path class="cls-2" d="M116.30029,140.42822H79.10938l-7.5669,23.50611a1.89431,1.89431,0,0,1-1.93213,1.449H50.77344q-1.61133,0-1.127-1.771L81.84619,70.87622q.4834-1.44873.96631-3.30054a34.478,34.478,0,0,0,.644-6.52026.99643.99643,0,0,1,1.127-1.1272h25.59863q1.125,0,1.28808.80518l36.54737,103.03979q.48267,1.61133-.96631,1.61011H126.12109a1.48824,1.48824,0,0,1-1.60986-1.12695Zm-31.395-20.28589h25.438q-.96606-3.21863-2.2539-7.24511-1.29054-4.02246-2.73682-8.61353-1.44947-4.58826-2.89844-9.177-1.44873-4.58826-2.65625-8.855-1.20776-4.26379-2.17334-7.80835h-.16113a130.10721,130.10721,0,0,1-3.38086,12.87988q-2.2566,7.24512-4.58887,14.812Q87.158,113.70484,84.90527,120.14233Z"/><path class="cls-2" d="M169.75049,76.99438a11.67783,11.67783,0,0,1-8.855-3.542,12.73665,12.73665,0,0,1-3.38135-9.177,11.813,11.813,0,0,1,3.62256-8.93555,12.44131,12.44131,0,0,1,8.93555-3.46142q5.79638,0,9.09668,3.46142a12.4294,12.4294,0,0,1,3.30029,8.93555,12.57378,12.57378,0,0,1-3.46143,9.177A12.3536,12.3536,0,0,1,169.75049,76.99438Zm-11.10938,86.77881v-76.958c0-.96582.42774-1.449,1.28809-1.449h19.80322q1.28687,0,1.28809,1.449v76.958q0,1.61133-1.28809,1.61011H160.09033Q158.6416,165.3833,158.64111,163.77319Z"/></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "incopy.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#49021f;}.cls-2{fill:#f36;}</style></defs><title>Asset 100</title><g id="Layer_2" data-name="Layer 2"><g id="Surfaces"><g id="Publishing_Surface" data-name="Publishing Surface"><g id="Outline_no_shadow" data-name="Outline no shadow"><rect class="cls-1" width="240" height="234" rx="42.5"/></g></g></g><g id="Outlined_Mnemonics_Logos" data-name="Outlined Mnemonics &amp; Logos"><g id="Ic"><path class="cls-2" d="M96.49609,61.21631V163.77319q0,1.61133-1.44873,1.61011H75.56592q-1.28981,0-1.2876-1.61011V61.21631q0-1.28686,1.44873-1.28809H95.208A1.13916,1.13916,0,0,1,96.49609,61.21631Z"/><path class="cls-2" d="M173.293,146.54639v16.583a1.58323,1.58323,0,0,1-.80469,1.449,32.223,32.223,0,0,1-8.5332,1.93188q-4.82959.483-9.33789.48316a49.15747,49.15747,0,0,1-17.8711-3.05908,36.90879,36.90879,0,0,1-13.36279-8.6941,39.42369,39.42369,0,0,1-8.45264-13.2019,43.96084,43.96084,0,0,1-2.97851-16.261,41.341,41.341,0,0,1,5.63525-21.65454,40.06428,40.06428,0,0,1,15.8584-14.89258q10.22241-5.47155,23.9082-5.47387a61.44138,61.44138,0,0,1,9.499.56347,44.25928,44.25928,0,0,1,5.47412,1.20752,1.8953,1.8953,0,0,1,.80517,1.771l-.16113,16.583q0,1.44909-1.28808,1.127a29.04977,29.04977,0,0,0-5.47413-1.36841,49.79892,49.79892,0,0,0-8.0498-.56347,28.46821,28.46821,0,0,0-11.91406,2.41479,18.9705,18.9705,0,0,0-8.37207,7.24512q-3.06006,4.83-3.05909,12.23608,0,8.373,3.62256,13.20191a19.61436,19.61436,0,0,0,9.17725,6.84253,32.59531,32.59531,0,0,0,11.18945,2.01245,68.49913,68.49913,0,0,0,7.728-.40259,28.146,28.146,0,0,0,5.47412-1.04639Q173.2915,145.25953,173.293,146.54639Z"/></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "192x192.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "indesign.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><defs><style>.cls-1{fill:#49021f;}.cls-2{fill:#f36;}</style></defs><title>256</title><g id="Surfaces"><g id="Publishing_Surface" data-name="Publishing Surface"><g id="Outline_no_shadow" data-name="Outline no shadow"><rect class="cls-1" x="8" y="4" width="240" height="234" rx="42.5"/></g></g></g><g id="Outlined_Mnemonics_Logos" data-name="Outlined Mnemonics &amp; Logos"><g id="Id"><path class="cls-2" d="M95.1582,65.21631V167.77319q0,1.61133-1.44873,1.61011H74.228q-1.28981,0-1.2876-1.61011V65.21631q0-1.28686,1.44873-1.28809h19.481A1.13916,1.13916,0,0,1,95.1582,65.21631Z"/><path class="cls-2" d="M152.79639,170.99341a49.88223,49.88223,0,0,1-21.49366-4.50806,34.17219,34.17219,0,0,1-15.05322-13.60449q-5.47559-9.09411-5.47412-22.78149a42.04123,42.04123,0,0,1,5.47412-21.09107,40.1871,40.1871,0,0,1,15.939-15.45605q10.46337-5.796,25.27685-5.7959.80347,0,2.09278.08056,1.28687.08277,3.05908.24146v-31.717c0-.74976.32226-1.127.96631-1.127h20.28564a.854.854,0,0,1,.96631.96582v95.15112q0,2.73962.24121,5.957.24169,3.22228.40283,5.7959a1.66418,1.66418,0,0,1-.96631,1.61011,79.86,79.86,0,0,1-16.26074,4.82983A87.29931,87.29931,0,0,1,152.79639,170.99341Zm9.8208-19.96411V107.07642a15.97072,15.97072,0,0,0-2.65625-.48316,32.10968,32.10968,0,0,0-3.30078-.16089,24.86085,24.86085,0,0,0-11.27,2.57593,22.00524,22.00524,0,0,0-8.45215,7.406q-3.30176,4.83-3.30078,12.719a28.39124,28.39124,0,0,0,1.69043,10.304,19.58772,19.58772,0,0,0,4.5083,7.084,17.16656,17.16656,0,0,0,6.76172,4.02515,26.49038,26.49038,0,0,0,8.2915,1.28784q2.25293,0,4.186-.16089A17.23154,17.23154,0,0,0,162.61719,151.0293Z"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "Lightroom Classic.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#001e36;}.cls-2{fill:#31a8ff;}</style></defs><title>Asset 180</title><g id="Layer_2" data-name="Layer 2"><g id="Surfaces"><g id="Photo_Surface" data-name="Photo Surface"><g id="Outline_no_shadow" data-name="Outline no shadow"><rect class="cls-1" width="240" height="234" rx="42.5"/></g></g></g><g id="Outlined_Mnemonics_Logos" data-name="Outlined Mnemonics &amp; Logos"><g id="LrC"><path class="cls-2" d="M84.192,165.3833H27.10131q-1.44954,0-1.44888-1.771V61.3772q0-1.44873,1.3043-1.449H44.63461c.67456,0,1.01425.37744,1.01425,1.1272v84.042H87.23519q1.30386,0,1.01426,1.449L85.6413,163.93433q-.14634.96606-.57964,1.20752A1.77863,1.77863,0,0,1,84.192,165.3833Z"/><path class="cls-2" d="M98.53617,85.36621h15.79394q1.30386,0,1.8835,1.61011a8.35226,8.35226,0,0,1,.72466,2.2539,28.71923,28.71923,0,0,1,.43462,3.13965q.143,1.69044.145,3.62232a34.80174,34.80174,0,0,1,9.63589-8.61328,24.98991,24.98991,0,0,1,13.40332-3.62256q1.30385,0,1.30385,1.449v19.481q0,1.1283-1.44887,1.127a32.23561,32.23561,0,0,0-9.49087.72461,33.15877,33.15877,0,0,0-7.53487,2.6565,15.39786,15.39786,0,0,0-4.85419,3.70288v51.03711q0,1.44909-1.15928,1.449H99.84q-1.4502,0-1.44888-1.61011V108.3894q0-3.53979-.07251-7.48657-.07449-3.94335-.21753-7.80859a56.54581,56.54581,0,0,0-.5792-6.60083,1.35031,1.35031,0,0,1,.21709-.8855A1.0463,1.0463,0,0,1,98.53617,85.36621Z"/><path class="cls-2" d="M212.62149,70.60112a25.83378,25.83378,0,0,0-6.67765-1.70558,75.39944,75.39944,0,0,0-10.19685-.58619c-9.80813,0-18.388,2.10233-25.50092,6.24858a42.56967,42.56967,0,0,0-16.555,17.51031c-3.85522,7.44733-5.81023,16.20145-5.81023,26.01991a63.596,63.596,0,0,0,2.24138,17.56717,46.1497,46.1497,0,0,0,6.353,13.67058,40.59588,40.59588,0,0,0,9.81019,9.9415,41.323,41.323,0,0,0,12.65638,6.03819,53.131,53.131,0,0,0,14.58865,1.98137,76.40988,76.40988,0,0,0,10.32505-.65856,32.8752,32.8752,0,0,0,8.05575-2.0005,2.55936,2.55936,0,0,0,1.4629-2.65854V147.05247a1.80324,1.80324,0,0,0-.50762-1.467c-.25019-.20729-.78056-.4549-1.56008.14267a28.74176,28.74176,0,0,1-6.96711,1.65984,78.829,78.829,0,0,1-8.72258.43008,30.76187,30.76187,0,0,1-7.84072-1.06279A29.61771,29.61771,0,0,1,180.1948,143.57a22.35682,22.35682,0,0,1-6.323-5.76526,28.55726,28.55726,0,0,1-4.38973-8.62954,38.36439,38.36439,0,0,1-1.59729-11.667,35.99278,35.99278,0,0,1,3.31762-15.99054,25.412,25.412,0,0,1,9.39562-10.5127,27.17413,27.17413,0,0,1,14.62793-3.8056,53.422,53.422,0,0,1,9.39355.78573,23.36188,23.36188,0,0,1,7.18113,2.3998,1.26058,1.26058,0,0,0,1.27991.00931,1.72374,1.72374,0,0,0,.60893-1.49443V72.96811A2.56436,2.56436,0,0,0,212.62149,70.60112Z"/></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "lightroom.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 240 234" style="enable-background:new 0 0 240 234;" xml:space="preserve">
<style type="text/css">
.st0{fill:#001E36;}
.st1{fill:#31A8FF;}
</style>
<g id="Layer_2_1_">
<g id="Surfaces">
<g id="Photo_Surface">
<g id="Outline_no_shadow">
<path class="st0" d="M42.5,0h155C221,0,240,19,240,42.5v149c0,23.5-19,42.5-42.5,42.5h-155C19,234,0,215,0,191.5v-149
C0,19,19,0,42.5,0z"/>
</g>
</g>
</g>
<g id="Outlined_Mnemonics_Logos">
<g id="Lr">
<path class="st1" d="M126,165.4H62.6c-1.1,0-1.6-0.6-1.6-1.8V61.4c-0.1-0.7,0.4-1.3,1.1-1.4c0.1,0,0.2,0,0.4,0h19.6
c0.5-0.1,1.1,0.3,1.1,0.8c0,0.1,0,0.2,0,0.3v84h46.2c1,0,1.3,0.5,1.1,1.4l-2.9,17.4c0,0.5-0.3,0.9-0.6,1.2
C126.7,165.3,126.4,165.4,126,165.4z"/>
<path class="st1" d="M142,85.4h17.5c1,0,1.8,0.7,2.1,1.6c0.4,0.7,0.7,1.5,0.8,2.3c0.2,1,0.4,2.1,0.5,3.1c0.1,1.1,0.2,2.3,0.2,3.6
c3-3.5,6.6-6.4,10.7-8.6c4.6-2.5,9.7-3.7,14.9-3.6c0.7-0.1,1.3,0.4,1.4,1.1c0,0.1,0,0.2,0,0.4v19.5c0,0.8-0.5,1.1-1.6,1.1
c-6.5-0.4-13,0.8-18.9,3.4c-2,0.9-3.9,2.1-5.4,3.7v51c0,1-0.4,1.4-1.3,1.4h-19.5c-0.8,0.1-1.5-0.4-1.6-1.2c0-0.1,0-0.3,0-0.4
v-55.4c0-2.4,0-4.9-0.1-7.5c-0.1-2.6-0.1-5.2-0.2-7.8c-0.1-2.2-0.3-4.4-0.6-6.6c-0.1-0.5,0.2-1,0.7-1.1
C141.7,85.3,141.8,85.3,142,85.4L142,85.4z"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "192x192 (1).png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "media-encoder.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#00005b;}.cls-2{fill:#99f;}</style></defs><title>Asset 106</title><g id="Layer_2" data-name="Layer 2"><g id="Surfaces"><g id="Video_Audio_Surface" data-name="Video/Audio Surface"><g id="Outline_no_shadow" data-name="Outline no shadow"><rect class="cls-1" width="240" height="234" rx="42.5"/></g></g></g><g id="Outlined_Mnemonics_Logos" data-name="Outlined Mnemonics &amp; Logos"><g id="Me"><path class="cls-2" d="M33.84038,60.89844a1.07979,1.07979,0,0,1,1.22368-.96606H63.20688a1.52823,1.52823,0,0,1,1.68245,1.1272q1.068,4.18835,2.6,10.0625,1.52729,5.87879,3.28836,12.96045,1.75689,7.08654,3.5945,14.49,1.83554,7.40735,3.59451,14.40942,1.7562,7.00343,2.9822,12.88013,1.22253,5.87879,1.9886,9.74048h.15308q.60882-3.38123,2.14121-9.01612,1.52658-5.63379,3.5175-12.63843,1.98651-7.00341,4.05327-14.40966,2.06514-7.40333,4.05328-14.65088,1.9858-7.24512,3.74712-13.44361,1.75689-6.19592,2.82959-10.38427a1.33567,1.33567,0,0,1,1.37675-1.1272h27.37791a1.21606,1.21606,0,0,1,1.37676,1.1272l3.67058,102.8789a1.073,1.073,0,0,1-.22915,1.04639,1.31054,1.31054,0,0,1-.99453.40259H116.12763a1.19507,1.19507,0,0,1-.76491-.24146,1.069,1.069,0,0,1-.30569-.8855q0-8.21118-.07654-17.22705-.07932-9.01464-.22962-18.11255-.15377-9.09375,0-17.4685.151-8.36938.15308-15.45606,0-7.08252.07654-12.3164.07515-5.22986.07654-7.8086H114.904q-.91776,4.0265-2.44736,10.54566-1.53077,6.52038-3.59405,14.65088-2.06512,8.13171-4.43549,16.82446-2.37337,8.69421-4.58858,17.22705-2.21891,8.53564-4.28288,15.939-2.06445,7.40735-3.44143,13.041a1.69161,1.69161,0,0,1-1.68245,1.28809H73.14848a1.6445,1.6445,0,0,1-1.83552-1.28809q-1.37629-5.63379-3.13528-13.041-1.76037-7.40332-3.59451-15.45581-1.83481-8.049-3.90019-16.50269-2.06443-8.45252-3.9002-16.34131-1.83482-7.88672-3.28835-14.812-1.45562-6.92066-2.67651-12.23608h-.30616v12.719q0,7.40734-.22915,16.34155-.2296,8.93555-.45876,18.676-.22962,9.743-.5353,20.125-.30825,10.38427-.76492,20.52734c0,.86035-.40867,1.28809-1.22368,1.28809H29.558a1.83142,1.83142,0,0,1-.99406-.24146q-.38409-.24169-.22962-1.20752Z"/><path class="cls-2" d="M196.67432,130.25122h-31.7168a20.74008,20.74008,0,0,0,3.05908,8.45264,16.56436,16.56436,0,0,0,7.3252,6.03735q4.90942,2.2566,12.7998,2.25415a58.43054,58.43054,0,0,0,10.38428-.8855,43.29305,43.29305,0,0,0,9.41846-2.81762c.53564-.42749.80517-.16089.80517.80517v15.29492a2.38427,2.38427,0,0,1-.2417,1.20752,2.30981,2.30981,0,0,1-.72461.72437,45.14814,45.14814,0,0,1-10.46484,3.46167,70.76093,70.76093,0,0,1-14.168,1.20752q-11.43384,0-19.15918-3.542a34.14679,34.14679,0,0,1-12.55762-9.499,37.19779,37.19779,0,0,1-6.92334-13.12158,51.668,51.668,0,0,1-2.09277-14.57056,50.74576,50.74576,0,0,1,2.49561-15.85839,41.42459,41.42459,0,0,1,7.48632-13.68506,35.96394,35.96394,0,0,1,12.0752-9.499,37.76341,37.76341,0,0,1,16.74365-3.46142,36.33453,36.33453,0,0,1,15.53662,3.05884,28.09974,28.09974,0,0,1,10.54541,8.2915,36.61534,36.61534,0,0,1,5.957,11.35059,40.44772,40.44772,0,0,1,1.93213,12.23608q0,3.54309-.2417,6.43994-.241,2.89784-.40234,4.186a1.45875,1.45875,0,0,1-1.44922,1.28808q-.96606,0-3.30029.24146-2.3357.24133-5.7959.322Q200.53565,130.25365,196.67432,130.25122Zm-31.7168-15.61694h21.09082q3.86425,0,5.71533-.08057a11.3564,11.3564,0,0,0,2.335-.24145v-.96607a12.88035,12.88035,0,0,0-.644-3.70288,13.15261,13.15261,0,0,0-13.041-9.177,13.98587,13.98587,0,0,0-13.28271,7.56689A20.32457,20.32457,0,0,0,164.95752,114.63428Z"/></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "192x192 (2).png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "photoshop.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#001e36;}.cls-2{fill:#31a8ff;}</style></defs><title>Asset 116</title><g id="Layer_2" data-name="Layer 2"><g id="Surfaces"><g id="Photo_Surface" data-name="Photo Surface"><g id="Outline_no_shadow" data-name="Outline no shadow"><rect class="cls-1" width="240" height="234" rx="42.5"/></g></g></g><g id="Outlined_Mnemonics_Logos" data-name="Outlined Mnemonics &amp; Logos"><g id="Ps"><path class="cls-2" d="M54.04167,164.09521V61.21631c0-.74976.32226-1.127.96631-1.127,1.71533,0,3.28157-.02515,5.64388-.08057q3.53979-.07911,7.64746-.16089,4.106-.07947,8.69433-.16113,4.5879-.07911,9.09619-.08057,12.23366,0,20.60791,3.05908a35.755,35.755,0,0,1,13.44385,8.21094,31.496,31.496,0,0,1,7.3252,11.35059,37.64894,37.64894,0,0,1,2.25439,12.96045q0,12.88256-5.957,21.252a33.65844,33.65844,0,0,1-16.1001,12.15552c-6.7622,2.52319-14.27636,3.3789-22.54,3.3789q-3.54345,0-4.99121-.08056-1.44873-.07947-4.34668-.08057v32.12183a1.28093,1.28093,0,0,1-1.44922,1.449H55.16862C54.41667,165.3833,54.04167,164.95557,54.04167,164.09521Zm21.74446-84.686v33.55493q2.09034.16224,3.86377.16089h5.313a37.7594,37.7594,0,0,0,11.51172-1.83765,17.35824,17.35824,0,0,0,8.21094-5.313q3.13915-3.70167,3.13965-10.304a16.28281,16.28281,0,0,0-2.335-8.85522,15.01394,15.01394,0,0,0-7.00341-5.71534A29.83951,29.83951,0,0,0,86.73389,79.0874q-3.86427,0-6.84229.08032Q76.91065,79.25085,75.78613,79.40918Z"/><path class="cls-2" d="M191.97114,106.863a37.6431,37.6431,0,0,0-9.57959-3.3811,50.875,50.875,0,0,0-11.18946-1.28809,20.82175,20.82175,0,0,0-6.03759.72461,5.42475,5.42475,0,0,0-3.13965,2.01245,5.0699,5.0699,0,0,0-.80469,2.73706,4.27537,4.27537,0,0,0,.96582,2.57593,10.95825,10.95825,0,0,0,3.38086,2.65649,67.449,67.449,0,0,0,7.084,3.30054,70.20083,70.20083,0,0,1,15.37549,7.32544,23.38242,23.38242,0,0,1,7.88916,8.2915A22.10738,22.10738,0,0,1,198.25,142.122a23.143,23.143,0,0,1-3.86377,13.28247,25.41573,25.41573,0,0,1-11.18995,8.93531q-7.32788,3.219-18.1123,3.22021a65.50368,65.50368,0,0,1-13.60449-1.28808,43.40843,43.40843,0,0,1-10.22363-3.22,2.08508,2.08508,0,0,1-1.127-1.93213V143.73187a.94571.94571,0,0,1,.40283-.8855.781.781,0,0,1,.88526.08057,43.01131,43.01131,0,0,0,12.397,4.9104,51.12181,51.12181,0,0,0,11.75293,1.52954q5.63379,0,8.2915-1.449a4.5512,4.5512,0,0,0,2.65674-4.186q0-2.09034-2.415-4.02491-2.41479-1.93212-9.82129-4.66918a59.18392,59.18392,0,0,1-14.24853-7.24488,24.5718,24.5718,0,0,1-7.5669-8.45263,22.20192,22.20192,0,0,1-2.33447-10.22339,23.08045,23.08045,0,0,1,3.38086-12.075,24.57046,24.57046,0,0,1,10.46533-9.177q7.08252-3.53943,17.71-3.542a78.40115,78.40115,0,0,1,12.397.8855,32.49681,32.49681,0,0,1,8.63066,2.33447,1.46829,1.46829,0,0,1,.96582.8855,4.44869,4.44869,0,0,1,.16113,1.20752v16.261a1.08221,1.08221,0,0,1-.48291.96606A1.556,1.556,0,0,1,191.97114,106.863Z"/></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "192x192 (3).png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "premiere-pro.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 240 234" style="enable-background:new 0 0 240 234;" xml:space="preserve">
<style type="text/css">
.st0{fill:#00005B;}
.st1{fill:#9999FF;}
</style>
<g id="Layer_2_1_">
<g id="Surfaces">
<g id="Video_Audio_Surface">
<g id="Outline_no_shadow">
<path class="st0" d="M42.5,0h155C221,0,240,19,240,42.5v149c0,23.5-19,42.5-42.5,42.5h-155C19,234,0,215,0,191.5v-149
C0,19,19,0,42.5,0z"/>
</g>
</g>
</g>
<g id="Outlined_Mnemonics_Logos">
<g id="Pr">
<path class="st1" d="M57,164.1V61.2c0-0.7,0.3-1.1,1-1.1c1.7,0,3.3,0,5.6-0.1c2.4-0.1,4.9-0.1,7.6-0.2c2.7-0.1,5.6-0.1,8.7-0.2
c3.1-0.1,6.1-0.1,9.1-0.1c8.2,0,15,1,20.6,3.1c5,1.7,9.6,4.5,13.4,8.2c3.2,3.2,5.7,7.1,7.3,11.4c1.5,4.2,2.3,8.5,2.3,13
c0,8.6-2,15.7-6,21.3s-9.6,9.8-16.1,12.2c-6.8,2.5-14.3,3.4-22.5,3.4c-2.4,0-4,0-5-0.1s-2.4-0.1-4.3-0.1V164
c0.1,0.7-0.4,1.3-1.1,1.4c-0.1,0-0.2,0-0.4,0h-19C57.4,165.4,57,165,57,164.1z M78.8,79.4V113c1.4,0.1,2.7,0.2,3.9,0.2H88
c3.9,0,7.8-0.6,11.5-1.8c3.2-0.9,6-2.8,8.2-5.3c2.1-2.5,3.1-5.9,3.1-10.3c0.1-3.1-0.7-6.2-2.3-8.9c-1.7-2.6-4.1-4.6-7-5.7
c-3.7-1.5-7.7-2.1-11.8-2c-2.6,0-4.9,0-6.8,0.1C80.9,79.2,79.5,79.3,78.8,79.4L78.8,79.4z"/>
<path class="st1" d="M146.6,85.2h17.5c1,0,1.8,0.7,2.1,1.6c0.3,0.8,0.5,1.6,0.6,2.5c0.2,1,0.4,2.1,0.5,3.1
c0.1,1.1,0.2,2.3,0.2,3.6c3-3.5,6.6-6.4,10.7-8.6c4.6-2.6,9.9-3.9,15.2-3.9c0.7-0.1,1.3,0.4,1.4,1.1c0,0.1,0,0.2,0,0.4v19.5
c0,0.8-0.5,1.1-1.6,1.1c-3.6-0.1-7.3,0.2-10.8,1c-2.9,0.6-5.7,1.5-8.4,2.7c-1.9,0.9-3.7,2.1-5.1,3.7v51c0,1-0.4,1.4-1.3,1.4
h-19.7c-0.8,0.1-1.5-0.4-1.6-1.2c0-0.1,0-0.3,0-0.4v-55.4c0-2.4,0-4.9-0.1-7.5s-0.1-5.2-0.2-7.8c0-2.3-0.2-4.5-0.4-6.8
c-0.1-0.5,0.2-1,0.7-1.1C146.3,85.1,146.5,85.1,146.6,85.2L146.6,85.2z"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "192x192 (4).png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "Premiere Rush.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><defs><style>.cls-1{fill:#00005b;}.cls-2{fill:#99f;}</style></defs><title>256</title><g id="Surfaces"><g id="Video_Audio_Surface" data-name="Video/Audio Surface"><g id="Outline_no_shadow" data-name="Outline no shadow"><rect class="cls-1" x="8" y="4" width="240" height="234" rx="42.5"/></g></g></g><g id="Live_Text" data-name="Live Text"><g id="Ru"><path class="cls-2" d="M68.67,169.38H49.52c-.86,0-1.29-.48-1.29-1.45V65.54c0-1,.32-1.45,1-1.45q5.64-.17,9.66-.24l7.73-.16c2.47-.06,5-.08,7.65-.08H83q13.53,0,22.7,3.78a30.67,30.67,0,0,1,14.09,11Q124.7,85.5,124.7,96A28.09,28.09,0,0,1,122.13,108a30.3,30.3,0,0,1-7.09,9.58,34.67,34.67,0,0,1-10.46,6.52q1.61,2.1,4.51,6.36t6.36,9.5q3.45,5.24,6.92,10.55t6.28,9.82c1.87,3,3.3,5.37,4.26,7.08a1.75,1.75,0,0,1,.41,1.29.66.66,0,0,1-.73.64H109.25a3.36,3.36,0,0,1-1.45-.24,2.14,2.14,0,0,1-.81-.72c-1.18-1.72-2.65-4-4.43-6.76s-3.64-5.91-5.63-9.34-4-6.82-6-10.15-3.73-6.33-5.23-9A11,11,0,0,0,83.89,131a3.24,3.24,0,0,0-2-.56H70v37.51C70,168.9,69.53,169.38,68.67,169.38Zm1.29-58H82.52a32.18,32.18,0,0,0,10.55-1.53,13.89,13.89,0,0,0,6.84-4.75,13,13,0,0,0,2.41-8q0-7.4-5.47-10.7t-14.49-3.3c-2.58,0-5,0-7.16.08s-4,.13-5.24.24Z"/><path class="cls-2" d="M209.55,90.49v61c0,2.69,0,5.07.08,7.17s.16,3.92.32,5.47.3,2.87.4,3.95c.11.86-.27,1.28-1.12,1.28H192a1.88,1.88,0,0,1-1.93-1.12c-.22-.75-.4-1.59-.57-2.5a14.48,14.48,0,0,1-.24-2.49A28.61,28.61,0,0,1,178,169.46,43.45,43.45,0,0,1,166.88,171,33.08,33.08,0,0,1,156,169.3a21.18,21.18,0,0,1-8.54-5.47,25.83,25.83,0,0,1-5.63-9.66,44.05,44.05,0,0,1-2-14.25V90.65a1.13,1.13,0,0,1,1.28-1.28h19.81a1.13,1.13,0,0,1,1.28,1.28v46.69q0,6.61,2.9,10.39t9.66,3.78a18.15,18.15,0,0,0,6.76-1.21,21.07,21.07,0,0,0,5.64-3.3V90.49c0-.75.48-1.12,1.45-1.12h19.8A1,1,0,0,1,209.55,90.49Z"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,22 +0,0 @@
{
"images" : [
{
"filename" : "Substance 3D Sampler (Beta) - 96x96.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Substance 3D Sampler (Beta) - 192x192.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,22 +0,0 @@
{
"images" : [
{
"filename" : "Substance 3D Sampler - 96x96.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Substance 3D Sampler - 192x192.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,9 +1,11 @@
//
// Adobe-Downloader
// Adobe Downloader
//
// Created by X1a0He on 2024/10/30.
//
import Foundation
import SwiftUI
enum PackageStatus: Equatable {
case waiting
case downloading
@@ -11,7 +13,7 @@ enum PackageStatus: Equatable {
case completed
case failed(String)
var description: String {
var description: LocalizedStringKey {
switch self {
case .waiting: return "等待中"
case .downloading: return "下载中"
@@ -68,6 +70,7 @@ enum NetworkError: Error, LocalizedError {
case applicationInfoError(String, Error?)
case unsupportedPlatform(String)
case incompatibleVersion(String, String)
case cancelled
var errorCode: Int {
switch self {
@@ -93,6 +96,7 @@ enum NetworkError: Error, LocalizedError {
case .applicationInfoError: return 7001
case .unsupportedPlatform: return 7002
case .incompatibleVersion: return 7003
case .cancelled: return 5004
}
}
@@ -151,6 +155,8 @@ enum NetworkError: Error, LocalizedError {
return NSLocalizedString("不支持的平台: \(platform)", comment: "Unsupported platform")
case .incompatibleVersion(let current, let required):
return NSLocalizedString("版本不兼容: 当前版本 \(current), 需要版本 \(required)", comment: "Incompatible version")
case .cancelled:
return NSLocalizedString("下载已取消", comment: "Download cancelled")
}
}

View File

@@ -1,24 +1,11 @@
//
// Adobe-Downloader
// Adobe Downloader
//
// Created by X1a0He on 2024/10/30.
//
import Foundation
import AppKit
extension FileManager {
func volumeAvailableCapacity(for url: URL) throws -> Int64 {
let resourceValues = try url.resourceValues(forKeys: [.volumeAvailableCapacityKey])
return Int64(resourceValues.volumeAvailableCapacity ?? 0)
}
}
extension Sap.Versions {
var size: Int64 {
return 0
}
}
extension NewDownloadTask {
var startTime: Date {
switch totalStatus {
@@ -30,13 +17,11 @@ extension NewDownloadTask {
return info.timestamp
case .paused(let info):
return info.timestamp
case .retrying(let info):
return info.nextRetryDate.addingTimeInterval(-60)
case .failed(let info):
return info.timestamp
case .waiting:
return Date()
case .none:
case .retrying(let info):
return info.nextRetryDate.addingTimeInterval(-60)
case .waiting, .none:
return createAt
}
}
@@ -54,7 +39,7 @@ extension NetworkManager {
for task in self.downloadTasks {
if case .paused(let info) = task.status,
info.reason == .networkIssue {
await self.resumeDownload(taskId: task.id)
await self.downloadUtils.resumeDownloadTask(taskId: task.id)
}
}
} else if wasConnected && !self.isConnected {

View File

@@ -1,5 +1,5 @@
//
// Adobe-Downloader
// Adobe Downloader
//
// Created by X1a0He on 2024/10/30.
//
@@ -30,4 +30,12 @@ struct AppStatics {
("hu_HU", "Magyar"),
("ALL", "所有语言")
]
static func isValidLanguageCode(_ code: String) -> Bool {
return supportedLanguages.contains { $0.code == code }
}
static func getLanguageName(for code: String) -> String? {
return supportedLanguages.first { $0.code == code }?.name
}
}

View File

@@ -1,5 +1,5 @@
//
// Adobe-Downloader
// Adobe Downloader
//
// Created by X1a0He on 2024/10/30.
//
@@ -97,6 +97,13 @@ class Package: Identifiable, ObservableObject {
var hasValidSize: Bool {
downloadSize > 0
}
func updateStatus(_ status: PackageStatus) {
Task { @MainActor in
self.status = status
objectWillChange.send()
}
}
}
class ProductsToDownload: ObservableObject {
var sapCode: String

View File

@@ -29,12 +29,13 @@ struct ContentView: View {
var body: some View {
VStack(spacing: 0) {
HStack(spacing: 16) {
HStack() {
Text("Adobe Downloader")
.font(.title2)
.fontWeight(.bold)
.frame(width: 180)
.fixedSize()
SettingsView(
useDefaultLanguage: $useDefaultLanguage,
useDefaultDirectory: $useDefaultDirectory,
@@ -45,7 +46,6 @@ struct ContentView: View {
HStack(spacing: 8) {
SearchField(text: $searchText)
.frame(width: 140)
Button(action: refreshData) {
Image(systemName: "arrow.clockwise")
@@ -73,10 +73,8 @@ struct ContentView: View {
}
)
}
.frame(width: 200)
}
.padding(.horizontal, 12)
.padding(.vertical, 8)
.background(Color(NSColor.windowBackgroundColor))
ZStack {
@@ -107,12 +105,10 @@ struct ContentView: View {
Button(action: {
networkManager.retryFetchData()
}) {
HStack(spacing: 8) {
HStack() {
Image(systemName: "arrow.clockwise")
Text("重试")
}
.padding(.horizontal, 20)
.padding(.vertical, 8)
}
.buttonStyle(.borderedProminent)
.controlSize(.large)

View File

@@ -1,5 +1,5 @@
//
// Adobe-Downloader
// Adobe Downloader
//
// Created by X1a0He on 2024/10/30.
//

View File

@@ -4,75 +4,6 @@ import Combine
import AppKit
import SwiftUI
private class DownloadDelegate: NSObject, URLSessionDownloadDelegate {
var completionHandler: (URL?, URLResponse?, Error?) -> Void
var progressHandler: ((Int64, Int64, Int64) -> Void)?
var destinationDirectory: URL
var fileName: String
init(destinationDirectory: URL,
fileName: String,
completionHandler: @escaping (URL?, URLResponse?, Error?) -> Void,
progressHandler: ((Int64, Int64, Int64) -> Void)? = nil) {
self.destinationDirectory = destinationDirectory
self.fileName = fileName
self.completionHandler = completionHandler
self.progressHandler = progressHandler
super.init()
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
do {
if !FileManager.default.fileExists(atPath: destinationDirectory.path) {
try FileManager.default.createDirectory(at: destinationDirectory, withIntermediateDirectories: true)
}
let destinationURL = destinationDirectory.appendingPathComponent(fileName)
if FileManager.default.fileExists(atPath: destinationURL.path) {
try FileManager.default.removeItem(at: destinationURL)
}
try FileManager.default.moveItem(at: location, to: destinationURL)
Thread.sleep(forTimeInterval: 0.5)
if FileManager.default.fileExists(atPath: destinationURL.path) {
_ = try FileManager.default.attributesOfItem(atPath: destinationURL.path)[.size] as? Int64 ?? 0
completionHandler(destinationURL, downloadTask.response, nil)
} else {
completionHandler(nil, downloadTask.response, NetworkError.fileSystemError("文件移动后不存在", nil))
}
} catch {
completionHandler(nil, downloadTask.response, error)
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard let error = error else { return }
switch (error as NSError).code {
case NSURLErrorCancelled:
return
case NSURLErrorTimedOut:
completionHandler(nil, task.response, NetworkError.downloadError("下载超时", error))
case NSURLErrorNotConnectedToInternet:
completionHandler(nil, task.response, NetworkError.noConnection)
default:
completionHandler(nil, task.response, error)
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64) {
guard totalBytesExpectedToWrite > 0 else { return }
guard bytesWritten > 0 else { return }
progressHandler?(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
}
func cleanup() {
completionHandler = { _, _, _ in }
progressHandler = nil
}
}
@MainActor
class NetworkManager: ObservableObject {
typealias ProgressUpdate = (bytesWritten: Int64, totalWritten: Int64, expectedToWrite: Int64)
@@ -110,9 +41,10 @@ class NetworkManager: ObservableObject {
await fetchProductsWithRetry()
}
func startDownload(sap: Sap, selectedVersion: String, language: String, destinationURL: URL) async throws {
guard let productInfo = self.saps[sap.sapCode]?.versions[selectedVersion] else {
throw NetworkError.invalidData("无法获取产品信息")
guard let productInfo = self.saps[sap.sapCode]?.versions[selectedVersion] else {
throw NetworkError.invalidData("无法获取产品信息")
}
let task = NewDownloadTask(
sapCode: sap.sapCode,
version: selectedVersion,
@@ -121,11 +53,7 @@ class NetworkManager: ObservableObject {
directory: destinationURL,
productsToDownload: [],
createAt: Date(),
totalStatus: .preparing(DownloadStatus.PrepareInfo(
message: "正在准备下载...",
timestamp: Date(),
stage: .initializing
)),
totalStatus: .preparing(DownloadStatus.PrepareInfo(message: "正在准备下载...", timestamp: Date(), stage: .initializing)),
totalProgress: 0,
totalDownloadedSize: 0,
totalSize: 0,
@@ -136,171 +64,7 @@ class NetworkManager: ObservableObject {
updateDockBadge()
do {
if task.sapCode == "APRO" {
try await downloadUtils.downloadAPRO(task: task, productInfo: productInfo)
return
}
try downloadUtils.createInstallerApp(
for: task.sapCode,
version: task.version,
language: task.language,
at: task.directory
)
var productsToDownload: [ProductsToDownload] = []
productsToDownload.append(ProductsToDownload(
sapCode: sap.sapCode,
version: selectedVersion,
buildGuid: productInfo.buildGuid
))
for dependency in productInfo.dependencies {
if let dependencyVersions = saps[dependency.sapCode]?.versions {
let sortedVersions = dependencyVersions.sorted { first, second in
first.value.productVersion.compare(second.value.productVersion, options: .numeric) == .orderedDescending
}
var firstGuid = ""
var buildGuid = ""
for (_, versionInfo) in sortedVersions where versionInfo.baseVersion == dependency.version {
if firstGuid.isEmpty { firstGuid = versionInfo.buildGuid }
if allowedPlatform.contains(versionInfo.apPlatform) {
buildGuid = versionInfo.buildGuid
break
}
}
if buildGuid.isEmpty { buildGuid = firstGuid }
if !buildGuid.isEmpty {
productsToDownload.append(ProductsToDownload(
sapCode: dependency.sapCode,
version: dependency.version,
buildGuid: buildGuid
))
}
}
}
for product in productsToDownload {
await MainActor.run {
task.setStatus(.preparing(DownloadStatus.PrepareInfo(
message: "正在处理 \(product.sapCode) 的产品信息...",
timestamp: Date(),
stage: .fetchingInfo
)))
objectWillChange.send()
}
let productDir = task.directory.appendingPathComponent("Contents/Resources/products/\(product.sapCode)")
if !FileManager.default.fileExists(atPath: productDir.path) {
try FileManager.default.createDirectory(at: productDir, withIntermediateDirectories: true)
}
await MainActor.run {
task.setStatus(.preparing(DownloadStatus.PrepareInfo(
message: "正在下载 \(product.sapCode) 的产品信息...",
timestamp: Date(),
stage: .fetchingInfo
)))
objectWillChange.send()
}
let jsonString = try await getApplicationInfo(buildGuid: product.buildGuid)
let jsonURL = productDir.appendingPathComponent("application.json")
try jsonString.write(to: jsonURL, atomically: true, encoding: .utf8)
guard let jsonData = jsonString.data(using: .utf8),
let appInfo = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
let packages = appInfo["Packages"] as? [String: Any],
let packageArray = packages["Package"] as? [[String: Any]] else {
throw NetworkError.invalidData("无法解析产品信息")
}
let totalPackages = packageArray.count
task.totalPackages = totalPackages
await MainActor.run {
task.setStatus(.downloading(DownloadStatus.DownloadInfo(
fileName: task.currentPackage?.fullPackageName ?? "",
currentPackageIndex: task.completedPackages,
totalPackages: task.totalPackages,
startTime: Date(),
estimatedTimeRemaining: nil
)))
objectWillChange.send()
}
for package in packageArray {
let fullPackageName: String
if let name = package["fullPackageName"] as? String, !name.isEmpty {
fullPackageName = name
} else if let name = package["PackageName"] as? String, !name.isEmpty {
fullPackageName = "\(name).zip"
} else {
continue
}
let packageType = package["Type"] as? String ?? "non-core"
let isLanguageSuitable: Bool
if packageType == "core" {
isLanguageSuitable = true
} else {
let condition = package["Condition"] as? String ?? ""
let osLang = Locale.current.identifier
isLanguageSuitable = (
task.language == "ALL" ||
condition.isEmpty ||
!condition.contains("[installLanguage]") ||
condition.contains("[installLanguage]==\(task.language)") ||
condition.contains("[installLanguage]==\(osLang)")
)
}
if isLanguageSuitable {
let downloadSize: Int64
if let sizeNumber = package["DownloadSize"] as? NSNumber {
downloadSize = sizeNumber.int64Value
} else if let sizeString = package["DownloadSize"] as? String,
let parsedSize = Int64(sizeString) {
downloadSize = parsedSize
} else if let sizeInt = package["DownloadSize"] as? Int {
downloadSize = Int64(sizeInt)
} else {
continue
}
guard let downloadURL = package["Path"] as? String,
!downloadURL.isEmpty else {
continue
}
let newPackage = Package(
type: packageType,
fullPackageName: fullPackageName,
downloadSize: downloadSize,
downloadURL: downloadURL
)
product.packages.append(newPackage)
}
}
}
task.productsToDownload = productsToDownload
task.totalSize = productsToDownload.reduce(0) { productSum, product in
productSum + product.packages.reduce(0) { packageSum, pkg in
packageSum + (pkg.downloadSize > 0 ? pkg.downloadSize : 0)
}
}
await downloadUtils.startDownloadProcess(task: task)
try await downloadUtils.handleDownload(task: task, productInfo: productInfo, allowedPlatform: allowedPlatform, saps: saps)
} catch {
await MainActor.run {
task.setStatus(.failed(DownloadStatus.FailureInfo(
@@ -315,483 +79,6 @@ class NetworkManager: ObservableObject {
}
}
private func validateAndStartDownload(task: NewDownloadTask) async throws {
try downloadUtils.createInstallerApp(
for: task.sapCode,
version: task.version,
language: task.language,
at: task.directory
)
await startDownloadProcess(task: task)
}
internal func startDownloadProcess(task: NewDownloadTask) async {
task.totalStatus = .preparing(DownloadStatus.PrepareInfo(
message: "正在准备下载...",
timestamp: Date(),
stage: .initializing
))
for product in task.productsToDownload {
let sapCode = product.sapCode
let productDir = task.directory.appendingPathComponent("Contents/Resources/products/\(sapCode)")
try? FileManager.default.createDirectory(at: productDir, withIntermediateDirectories: true)
for package in product.packages {
task.currentPackage = package
let downloadURL = cdn + package.downloadURL
guard let url = URL(string: downloadURL) else { continue }
var request = URLRequest(url: url)
NetworkConstants.downloadHeaders.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
let delegate = DownloadDelegate(
destinationDirectory: task.directory.appendingPathComponent("Contents/Resources/products/\(sapCode)"),
fileName: package.fullPackageName,
completionHandler: { [weak self] localURL, response, error in
if let error = error {
Task {
await self?.handleError(task.id, error)
}
return
}
package.downloaded = true
package.progress = 1.0
package.status = .completed
let totalDownloaded = task.productsToDownload.reduce(0) { sum, product in
sum + product.packages.reduce(0) { sum, pkg in
sum + (pkg.downloaded ? pkg.downloadSize : 0)
}
}
let totalSize = task.productsToDownload.reduce(0) { sum, product in
sum + product.packages.reduce(0) { sum, pkg in pkg.downloadSize }
}
task.totalProgress = Double(totalDownloaded) / Double(totalSize)
},
progressHandler: { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
package.downloadedSize = totalBytesWritten
package.progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
package.speed = Double(bytesWritten)
package.status = .downloading
task.totalDownloadedSize = totalBytesWritten
task.totalSize = totalBytesExpectedToWrite
task.totalSpeed = Double(bytesWritten)
}
)
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
let downloadTask = session.downloadTask(with: request)
downloadTask.resume()
await withCheckedContinuation { continuation in
delegate.completionHandler = { _, _, _ in
continuation.resume()
}
}
}
}
let driverXml = downloadUtils.generateDriverXML(
sapCode: task.sapCode,
version: task.version,
language: task.language,
productInfo: (self.saps[task.sapCode]?.versions[task.version])!,
displayName: task.displayName
)
try? driverXml.write(
to: task.directory.appendingPathComponent("Contents/Resources/products/driver.xml"),
atomically: true,
encoding: .utf8
)
task.totalStatus = .completed(DownloadStatus.CompletionInfo(
timestamp: Date(),
totalTime: Date().timeIntervalSince(task.createAt),
totalSize: task.totalSize
))
}
private func performDownload(task: NewDownloadTask) async throws {
if task.sapCode == "APRO" {
if let productInfo = self.saps[task.sapCode]?.versions[task.version] {
try await downloadUtils.downloadAPRO(task: task, productInfo: productInfo)
} else {
throw NetworkError.invalidData("无法获取APRO产品信息")
}
return
}
try downloadUtils.createInstallerApp(
for: task.sapCode,
version: task.version,
language: task.language,
at: task.directory
)
try await downloadUtils.signApp(at: task.directory)
let productsDir = task.directory.appendingPathComponent("Contents/Resources/products")
try FileManager.default.createDirectory(at: productsDir, withIntermediateDirectories: true)
for product in task.productsToDownload {
let sapCode = product.sapCode
let version = product.version
print("[\(sapCode)_\(version)] Downloading application.json")
let jsonString = try await getApplicationInfo(buildGuid: product.buildGuid)
print("[\(sapCode)_\(version)] Creating folder for product")
let productDir = productsDir.appendingPathComponent(sapCode)
try FileManager.default.createDirectory(at: productDir, withIntermediateDirectories: true)
print("[\(sapCode)_\(version)] Saving application.json")
try jsonString.write(to: productDir.appendingPathComponent("application.json"),
atomically: true,
encoding: .utf8)
}
if let productInfo = self.saps[task.sapCode]?.versions[task.version] {
let driverXml = downloadUtils.generateDriverXML(
sapCode: task.sapCode,
version: task.version,
language: task.language,
productInfo: productInfo,
displayName: task.displayName
)
try driverXml.write(
to: productsDir.appendingPathComponent("driver.xml"),
atomically: true,
encoding: .utf8
)
}
await resumeDownload(taskId: task.id)
}
func pauseDownload(taskId: UUID) {
Task {
if let task = downloadTasks.first(where: { $0.id == taskId }) {
await MainActor.run {
task.setStatus(.paused(DownloadStatus.PauseInfo(
reason: .userRequested,
timestamp: Date(),
resumable: true
)))
updateDockBadge()
objectWillChange.send()
}
await cancelTracker.pause(taskId)
}
}
}
func resumeDownload(taskId: UUID) async {
if let task = downloadTasks.first(where: { $0.id == taskId }) {
await MainActor.run {
task.setStatus(.downloading(DownloadStatus.DownloadInfo(
fileName: task.currentPackage?.fullPackageName ?? "",
currentPackageIndex: 0,
totalPackages: task.productsToDownload.reduce(0) { $0 + $1.packages.count },
startTime: Date(),
estimatedTimeRemaining: nil
)))
updateDockBadge()
objectWillChange.send()
}
await downloadUtils.startDownloadProcess(task: task)
}
}
func cancelDownload(taskId: UUID, removeFiles: Bool = true) {
Task {
if let task = downloadTasks.first(where: { $0.id == taskId }) {
await MainActor.run {
task.setStatus(.failed(DownloadStatus.FailureInfo(
message: "下载已取消",
error: NetworkError.downloadCancelled,
timestamp: Date(),
recoverable: false
)))
updateDockBadge()
objectWillChange.send()
}
await cancelTracker.cancel(taskId)
if removeFiles {
try? FileManager.default.removeItem(at: task.directory)
}
}
}
}
func clearCompletedTasks() {
Task {
for task in downloadTasks {
if case .completed = task.status {
try? FileManager.default.removeItem(at: task.directory)
}
}
await MainActor.run {
downloadTasks.removeAll { task in
if case .completed = task.status {
return true
}
return false
}
updateDockBadge()
objectWillChange.send()
}
}
}
private func setupNetworkMonitoring() {
configureNetworkMonitor()
}
private func handleDownloadError(taskId: UUID, error: Error) async {
guard let index = downloadTasks.firstIndex(where: { $0.id == taskId }) else { return }
let task = downloadTasks[index]
let (errorMessage, isRecoverable) = classifyError(error)
if isRecoverable && task.retryCount < NetworkConstants.maxRetryAttempts {
task.retryCount += 1
let nextRetryDate = Date().addingTimeInterval(TimeInterval(NetworkConstants.retryDelay / 1_000_000_000))
task.setStatus(.retrying(DownloadStatus.RetryInfo(
attempt: task.retryCount,
maxAttempts: NetworkConstants.maxRetryAttempts,
reason: errorMessage,
nextRetryDate: nextRetryDate
)))
Task {
do {
try await Task.sleep(nanoseconds: NetworkConstants.retryDelay)
if await !cancelTracker.isCancelled(taskId) {
await downloadUtils.resumeDownloadTask(taskId: taskId)
}
} catch {
print("Retry cancelled for task: \(taskId)")
}
}
} else {
task.setStatus(.failed(DownloadStatus.FailureInfo(
message: errorMessage,
error: error,
timestamp: Date(),
recoverable: isRecoverable
)))
progressObservers[taskId]?.invalidate()
progressObservers.removeValue(forKey: taskId)
if let currentPackage = task.currentPackage {
let destinationDir = task.directory
.appendingPathComponent("Contents/Resources/products/\(task.sapCode)")
let fileURL = destinationDir.appendingPathComponent(currentPackage.fullPackageName)
try? FileManager.default.removeItem(at: fileURL)
}
updateDockBadge()
objectWillChange.send()
}
}
private func classifyError(_ error: Error) -> (message: String, recoverable: Bool) {
switch error {
case let networkError as NetworkError:
switch networkError {
case .noConnection:
return ("网络连接已断开", true)
case .timeout:
return ("下载超时", true)
case .serverUnreachable:
return ("服务器无法访问", true)
case .insufficientStorage:
return ("存储空间不足", false)
case .filePermissionDenied:
return ("没有入权限", false)
default:
return (networkError.localizedDescription, false)
}
case let urlError as URLError:
switch urlError.code {
case .notConnectedToInternet:
return ("网络连接已开", true)
case .timedOut:
return ("连接超时", true)
case .cancelled:
return ("下载已取消", false)
default:
return (urlError.localizedDescription, true)
}
default:
return (error.localizedDescription, false)
}
}
private func updateProgress(for taskId: UUID, progress: ProgressUpdate) {
guard let index = downloadTasks.firstIndex(where: { $0.id == taskId }) else { return }
let task = downloadTasks[index]
guard let currentPackage = task.currentPackage else { return }
let now = Date()
let timeDiff = now.timeIntervalSince(currentPackage.lastUpdated)
if timeDiff >= NetworkConstants.progressUpdateInterval {
Task { @MainActor in
currentPackage.updateProgress(
downloadedSize: progress.totalWritten,
speed: Double(progress.bytesWritten)
)
let totalDownloaded = task.productsToDownload.reduce(Int64(0)) { sum, prod in
sum + prod.packages.reduce(Int64(0)) { sum, pkg in
if pkg.downloaded {
return sum + pkg.downloadSize
} else if pkg.id == currentPackage.id {
return sum + progress.totalWritten
}
return sum
}
}
task.totalDownloadedSize = totalDownloaded
task.totalProgress = Double(totalDownloaded) / Double(task.totalSize)
task.totalSpeed = currentPackage.speed
currentPackage.lastRecordedSize = progress.totalWritten
currentPackage.lastUpdated = now
task.objectWillChange.send()
objectWillChange.send()
}
}
}
private func updateTaskStatus(_ taskId: UUID, _ status: DownloadStatus) async {
await MainActor.run {
if let index = downloadTasks.firstIndex(where: { $0.id == taskId }) {
downloadTasks[index].setStatus(status)
switch status {
case .completed:
progressObservers[taskId]?.invalidate()
progressObservers.removeValue(forKey: taskId)
if activeDownloadTaskId == taskId {
activeDownloadTaskId = nil
}
case .failed:
progressObservers[taskId]?.invalidate()
progressObservers.removeValue(forKey: taskId)
if activeDownloadTaskId == taskId {
activeDownloadTaskId = nil
}
case .downloading:
activeDownloadTaskId = taskId
case .paused:
if activeDownloadTaskId == taskId {
activeDownloadTaskId = nil
}
default:
break
}
updateDockBadge()
objectWillChange.send()
}
}
}
private func clampProgress(_ value: Double) -> Double {
min(1.0, max(0.0, value))
}
func retryFetchData() {
Task {
isFetchingProducts = false
loadingState = .idle
await fetchProducts()
}
}
func getActiveTaskId() async -> UUID? {
await MainActor.run { activeDownloadTaskId }
}
func setTaskStatus(_ taskId: UUID, _ status: DownloadStatus) async {
await updateTaskStatus(taskId, status)
}
func getTasks() async -> [NewDownloadTask] {
await MainActor.run { downloadTasks }
}
func handleError(_ taskId: UUID, _ error: Error) async {
guard let index = downloadTasks.firstIndex(where: { $0.id == taskId }) else { return }
let task = downloadTasks[index]
let (errorMessage, isRecoverable) = classifyError(error)
if isRecoverable && task.retryCount < NetworkConstants.maxRetryAttempts {
task.retryCount += 1
let nextRetryDate = Date().addingTimeInterval(TimeInterval(NetworkConstants.retryDelay / 1_000_000_000))
task.setStatus(.retrying(DownloadStatus.RetryInfo(
attempt: task.retryCount,
maxAttempts: NetworkConstants.maxRetryAttempts,
reason: errorMessage,
nextRetryDate: nextRetryDate
)))
Task {
do {
try await Task.sleep(nanoseconds: NetworkConstants.retryDelay)
if await !cancelTracker.isCancelled(taskId) {
await downloadUtils.resumeDownloadTask(taskId: taskId)
}
} catch {
print("Retry cancelled for task: \(taskId)")
}
}
} else {
task.setStatus(.failed(DownloadStatus.FailureInfo(
message: errorMessage,
error: error,
timestamp: Date(),
recoverable: isRecoverable
)))
progressObservers[taskId]?.invalidate()
progressObservers.removeValue(forKey: taskId)
if let currentPackage = task.currentPackage {
let destinationDir = task.directory
.appendingPathComponent("Contents/Resources/products/\(task.sapCode)")
let fileURL = destinationDir.appendingPathComponent(currentPackage.fullPackageName)
try? FileManager.default.removeItem(at: fileURL)
}
updateDockBadge()
objectWillChange.send()
}
}
func updateDownloadProgress(for taskId: UUID, progress: ProgressUpdate) {
updateProgress(for: taskId, progress: progress)
}
var cdnUrl: String {
get async {
await MainActor.run { cdn }
@@ -1059,14 +346,6 @@ class NetworkManager: ObservableObject {
return result
}
@MainActor
func updateTaskStatus(_ taskId: UUID, status: DownloadStatus) {
if let index = downloadTasks.firstIndex(where: { $0.id == taskId }) {
downloadTasks[index].setStatus(status)
objectWillChange.send()
}
}
func isVersionDownloaded(sap: Sap, version: String, language: String) -> URL? {
let platform = sap.versions[version]?.apPlatform ?? "unknown"
var fileName = ""
@@ -1111,4 +390,16 @@ class NetworkManager: ObservableObject {
NSApplication.shared.dockTile.badgeLabel = nil
}
}
private func setupNetworkMonitoring() {
configureNetworkMonitor()
}
func retryFetchData() {
Task {
isFetchingProducts = false
loadingState = .idle
await fetchProducts()
}
}
}

View File

@@ -1,5 +1,5 @@
//
// Adobe-Downloader
// Adobe Downloader
//
// Created by X1a0He on 2024/10/30.
//

View File

@@ -1,5 +1,5 @@
//
// Adobe-Downloader
// Adobe Downloader
//
// Created by X1a0He on 2024/10/30.
//
@@ -103,41 +103,55 @@ class DownloadUtils {
}
func pauseDownloadTask(taskId: UUID, reason: DownloadStatus.PauseInfo.PauseReason) async {
if let task = await networkManager?.downloadTasks.first(where: { $0.id == taskId }) {
task.setStatus(.paused(DownloadStatus.PauseInfo(
reason: reason,
timestamp: Date(),
resumable: true
)))
await cancelTracker.pause(taskId)
await MainActor.run {
if let task = networkManager?.downloadTasks.first(where: { $0.id == taskId }) {
task.setStatus(.paused(DownloadStatus.PauseInfo(
reason: reason,
timestamp: Date(),
resumable: true
)))
}
}
await cancelTracker.pause(taskId)
}
func resumeDownloadTask(taskId: UUID) async {
await MainActor.run {
if let task = networkManager?.downloadTasks.first(where: { $0.id == taskId }) {
task.setStatus(.downloading(DownloadStatus.DownloadInfo(
fileName: task.currentPackage?.fullPackageName ?? "",
currentPackageIndex: 0,
totalPackages: task.productsToDownload.reduce(0) { $0 + $1.packages.count },
startTime: Date(),
estimatedTimeRemaining: nil
)))
}
}
if let task = await networkManager?.downloadTasks.first(where: { $0.id == taskId }) {
task.setStatus(.downloading(DownloadStatus.DownloadInfo(
fileName: task.currentPackage?.fullPackageName ?? "",
currentPackageIndex: 0,
totalPackages: task.productsToDownload.reduce(0) { $0 + $1.packages.count },
startTime: Date(),
estimatedTimeRemaining: nil
)))
await startDownloadProcess(task: task)
}
}
func cancelDownloadTask(taskId: UUID, removeFiles: Bool = false) async {
await cancelTracker.cancel(taskId)
if let task = await networkManager?.downloadTasks.first(where: { $0.id == taskId }) {
if removeFiles {
try? FileManager.default.removeItem(at: task.directory)
await MainActor.run {
if let task = networkManager?.downloadTasks.first(where: { $0.id == taskId }) {
if removeFiles {
try? FileManager.default.removeItem(at: task.directory)
}
task.setStatus(.failed(DownloadStatus.FailureInfo(
message: "下载已取消",
error: NetworkError.downloadCancelled,
timestamp: Date(),
recoverable: false
)))
networkManager?.updateDockBadge()
networkManager?.objectWillChange.send()
}
task.setStatus(.failed(DownloadStatus.FailureInfo(
message: "下载已取消",
error: NetworkError.downloadCancelled,
timestamp: Date(),
recoverable: false
)))
}
}
@@ -151,14 +165,11 @@ class DownloadUtils {
func createInstallerApp(for sapCode: String, version: String, language: String, at destinationURL: URL) throws {
let parentDirectory = destinationURL.deletingLastPathComponent()
print(parentDirectory)
if !FileManager.default.fileExists(atPath: parentDirectory.path) {
try FileManager.default.createDirectory(at: parentDirectory, withIntermediateDirectories: true)
}
if FileManager.default.fileExists(atPath: destinationURL.path) {
try FileManager.default.removeItem(at: destinationURL)
}
if FileManager.default.fileExists(atPath: destinationURL.path) { try FileManager.default.removeItem(at: destinationURL) }
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/osacompile")
@@ -166,29 +177,20 @@ class DownloadUtils {
let tempScriptURL = FileManager.default.temporaryDirectory.appendingPathComponent("installer.js")
try NetworkConstants.INSTALL_APP_APPLE_SCRIPT.write(to: tempScriptURL, atomically: true, encoding: .utf8)
process.arguments = [
"-l", "JavaScript",
"-o", destinationURL.path,
tempScriptURL.path
]
process.arguments = ["-l", "JavaScript", "-o", destinationURL.path, tempScriptURL.path]
try process.run()
process.waitUntilExit()
if process.terminationStatus != 0 {
throw NetworkError.fileSystemError(
"Failed to create installer app: Exit code \(process.terminationStatus)",
nil
)
throw NetworkError.fileSystemError("Failed to create installer app: Exit code \(process.terminationStatus)", nil)
}
try? FileManager.default.removeItem(at: tempScriptURL)
let iconDestination = destinationURL.appendingPathComponent("Contents/Resources/applet.icns")
if FileManager.default.fileExists(atPath: iconDestination.path) {
try FileManager.default.removeItem(at: iconDestination)
}
if FileManager.default.fileExists(atPath: iconDestination.path) { try FileManager.default.removeItem(at: iconDestination) }
if FileManager.default.fileExists(atPath: NetworkConstants.ADOBE_CC_MAC_ICON_PATH) {
try FileManager.default.copyItem(
at: URL(fileURLWithPath: NetworkConstants.ADOBE_CC_MAC_ICON_PATH),
@@ -270,14 +272,8 @@ class DownloadUtils {
internal func startDownloadProcess(task: NewDownloadTask) async {
actor DownloadProgress {
var currentPackageIndex: Int = 0
func increment() {
currentPackageIndex += 1
}
func get() -> Int {
return currentPackageIndex
}
func increment() { currentPackageIndex += 1 }
func get() -> Int { return currentPackageIndex }
}
let progress = DownloadProgress()
@@ -304,13 +300,8 @@ class DownloadUtils {
productInfo: productInfo,
displayName: task.displayName
)
do {
try driverXml.write(
to: driverPath,
atomically: true,
encoding: .utf8
)
try driverXml.write(to: driverPath, atomically: true, encoding: .utf8)
} catch {
print("Error generating driver.xml:", error.localizedDescription)
await MainActor.run {
@@ -360,7 +351,7 @@ class DownloadUtils {
try await downloadPackage(package: package, task: task, product: product, url: url)
} catch {
print("Error downloading \(package.fullPackageName): \(error.localizedDescription)")
await networkManager?.handleError(task.id, error)
await self.handleError(task.id, error)
return
}
}
@@ -422,8 +413,8 @@ class DownloadUtils {
task.totalProgress = Double(totalDownloaded) / Double(totalSize)
task.totalSpeed = 0
let allCompleted = task.productsToDownload.allSatisfy { product in
product.packages.allSatisfy { $0.downloaded }
let allCompleted = task.productsToDownload.allSatisfy {
product in product.packages.allSatisfy { $0.downloaded }
}
if allCompleted {
@@ -516,7 +507,7 @@ class DownloadUtils {
if let cdn = await networkManager?.cdnUrl {
try await downloadPackage(package: package, task: task, product: product, url: URL(string: cdn + package.downloadURL)!)
} else {
throw NetworkError.invalidData("无法取 CDN 地址")
throw NetworkError.invalidData("无法取 CDN 地址")
}
}
}
@@ -525,7 +516,6 @@ class DownloadUtils {
guard let networkManager = networkManager else { return }
let manifestURL = await networkManager.cdnUrl + productInfo.buildGuid
print("Manifest URL:", manifestURL)
guard let url = URL(string: manifestURL) else {
throw NetworkError.invalidURL(manifestURL)
}
@@ -670,4 +660,320 @@ class DownloadUtils {
}
}
}
func handleDownload(task: NewDownloadTask, productInfo: Sap.Versions, allowedPlatform: [String], saps: [String: Sap]) async throws {
if task.sapCode == "APRO" {
try await downloadAPRO(task: task, productInfo: productInfo)
return
}
try createInstallerApp(
for: task.sapCode,
version: task.version,
language: task.language,
at: task.directory
)
var productsToDownload: [ProductsToDownload] = []
productsToDownload.append(ProductsToDownload(
sapCode: task.sapCode,
version: task.version,
buildGuid: productInfo.buildGuid
))
for dependency in productInfo.dependencies {
if let dependencyVersions = saps[dependency.sapCode]?.versions {
let sortedVersions = dependencyVersions.sorted { first, second in
first.value.productVersion.compare(second.value.productVersion, options: .numeric) == .orderedDescending
}
var firstGuid = "", buildGuid = ""
for (_, versionInfo) in sortedVersions where versionInfo.baseVersion == dependency.version {
if firstGuid.isEmpty { firstGuid = versionInfo.buildGuid }
if allowedPlatform.contains(versionInfo.apPlatform) {
buildGuid = versionInfo.buildGuid
break
}
}
if buildGuid.isEmpty { buildGuid = firstGuid }
if !buildGuid.isEmpty {
productsToDownload.append(ProductsToDownload(
sapCode: dependency.sapCode,
version: dependency.version,
buildGuid: buildGuid
))
}
}
}
for product in productsToDownload {
await MainActor.run {
task.setStatus(.preparing(DownloadStatus.PrepareInfo(
message: "正在处理 \(product.sapCode) 的包信息...",
timestamp: Date(),
stage: .fetchingInfo
)))
}
let productDir = task.directory.appendingPathComponent("Contents/Resources/products/\(product.sapCode)")
if !FileManager.default.fileExists(atPath: productDir.path) {
try FileManager.default.createDirectory(at: productDir, withIntermediateDirectories: true)
}
let jsonString = try await getApplicationInfo(buildGuid: product.buildGuid)
let jsonURL = productDir.appendingPathComponent("application.json")
try jsonString.write(to: jsonURL, atomically: true, encoding: .utf8)
guard let jsonData = jsonString.data(using: .utf8),
let appInfo = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
let packages = appInfo["Packages"] as? [String: Any],
let packageArray = packages["Package"] as? [[String: Any]] else {
throw NetworkError.invalidData("无法解析产品信息")
}
for package in packageArray {
let fullPackageName: String
if let name = package["fullPackageName"] as? String, !name.isEmpty {
fullPackageName = name
} else if let name = package["PackageName"] as? String, !name.isEmpty {
fullPackageName = "\(name).zip"
} else { continue }
let packageType = package["Type"] as? String ?? "non-core"
let isLanguageSuitable: Bool
if packageType == "core" {
isLanguageSuitable = true
} else {
let condition = package["Condition"] as? String ?? ""
let osLang = Locale.current.identifier
isLanguageSuitable = (
task.language == "ALL" || condition.isEmpty ||
!condition.contains("[installLanguage]") || condition.contains("[installLanguage]==\(task.language)") ||
condition.contains("[installLanguage]==\(osLang)")
)
}
if isLanguageSuitable {
let downloadSize: Int64
if let sizeNumber = package["DownloadSize"] as? NSNumber {
downloadSize = sizeNumber.int64Value
} else if let sizeString = package["DownloadSize"] as? String,
let parsedSize = Int64(sizeString) {
downloadSize = parsedSize
} else if let sizeInt = package["DownloadSize"] as? Int {
downloadSize = Int64(sizeInt)
} else { continue }
guard let downloadURL = package["Path"] as? String, !downloadURL.isEmpty else { continue }
let newPackage = Package(
type: packageType,
fullPackageName: fullPackageName,
downloadSize: downloadSize,
downloadURL: downloadURL
)
product.packages.append(newPackage)
}
}
}
let finalProducts = productsToDownload
let totalSize = finalProducts.reduce(0) { productSum, product in
productSum + product.packages.reduce(0) { packageSum, pkg in
packageSum + (pkg.downloadSize > 0 ? pkg.downloadSize : 0)
}
}
await MainActor.run {
task.productsToDownload = finalProducts
task.totalSize = totalSize
}
await startDownloadProcess(task: task)
}
func getApplicationInfo(buildGuid: String) async throws -> String {
guard let url = URL(string: NetworkConstants.applicationJsonURL) else {
throw NetworkError.invalidURL(NetworkConstants.applicationJsonURL)
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
var headers = NetworkConstants.adobeRequestHeaders
headers["x-adobe-build-guid"] = buildGuid
headers["Cookie"] = await networkManager?.generateCookie() ?? ""
headers.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkError.invalidResponse
}
guard (200...299).contains(httpResponse.statusCode) else {
throw NetworkError.httpError(httpResponse.statusCode, String(data: data, encoding: .utf8))
}
guard let jsonString = String(data: data, encoding: .utf8) else {
throw NetworkError.invalidData("无法将响应数据转换为json字符串")
}
return jsonString
}
func handleError(_ taskId: UUID, _ error: Error) async {
guard let task = await networkManager?.downloadTasks.first(where: { $0.id == taskId }) else { return }
let (errorMessage, isRecoverable) = classifyError(error)
if isRecoverable && task.retryCount < NetworkConstants.maxRetryAttempts {
task.retryCount += 1
let nextRetryDate = Date().addingTimeInterval(TimeInterval(NetworkConstants.retryDelay / 1_000_000_000))
task.setStatus(.retrying(DownloadStatus.RetryInfo(
attempt: task.retryCount,
maxAttempts: NetworkConstants.maxRetryAttempts,
reason: errorMessage,
nextRetryDate: nextRetryDate
)))
Task {
do {
try await Task.sleep(nanoseconds: NetworkConstants.retryDelay)
if await !cancelTracker.isCancelled(taskId) {
await resumeDownloadTask(taskId: taskId)
}
} catch {
print("Retry cancelled for task: \(taskId)")
}
}
} else {
await MainActor.run {
task.setStatus(.failed(DownloadStatus.FailureInfo(
message: errorMessage,
error: error,
timestamp: Date(),
recoverable: isRecoverable
)))
if let currentPackage = task.currentPackage {
let destinationDir = task.directory
.appendingPathComponent("Contents/Resources/products/\(task.sapCode)")
let fileURL = destinationDir.appendingPathComponent(currentPackage.fullPackageName)
try? FileManager.default.removeItem(at: fileURL)
}
networkManager?.updateDockBadge()
networkManager?.objectWillChange.send()
}
}
}
private func classifyError(_ error: Error) -> (message: String, recoverable: Bool) {
switch error {
case let networkError as NetworkError:
switch networkError {
case .noConnection:
return ("网络连接已断开", true)
case .timeout:
return ("下载超时", true)
case .serverUnreachable:
return ("服务器无法访问", true)
case .insufficientStorage:
return ("存储空间不足", false)
case .filePermissionDenied:
return ("没有入权限", false)
default:
return (networkError.localizedDescription, false)
}
case let urlError as URLError:
switch urlError.code {
case .notConnectedToInternet:
return ("网络连接已开", true)
case .timedOut:
return ("连接超时", true)
case .cancelled:
return ("下载已取消", false)
default:
return (urlError.localizedDescription, true)
}
default:
return (error.localizedDescription, false)
}
}
@MainActor
func updateProgress(for taskId: UUID, progress: ProgressUpdate) {
guard let task = networkManager?.downloadTasks.first(where: { $0.id == taskId }),
let currentPackage = task.currentPackage else { return }
let now = Date()
let timeDiff = now.timeIntervalSince(currentPackage.lastUpdated)
if timeDiff >= NetworkConstants.progressUpdateInterval {
currentPackage.updateProgress(
downloadedSize: progress.totalWritten,
speed: Double(progress.bytesWritten)
)
let totalDownloaded = task.productsToDownload.reduce(Int64(0)) { sum, prod in
sum + prod.packages.reduce(Int64(0)) { sum, pkg in
if pkg.downloaded {
return sum + pkg.downloadSize
} else if pkg.id == currentPackage.id {
return sum + progress.totalWritten
}
return sum
}
}
task.totalDownloadedSize = totalDownloaded
task.totalProgress = Double(totalDownloaded) / Double(task.totalSize)
task.totalSpeed = currentPackage.speed
currentPackage.lastRecordedSize = progress.totalWritten
currentPackage.lastUpdated = now
task.objectWillChange.send()
networkManager?.objectWillChange.send()
}
}
@MainActor
func updateTaskStatus(_ taskId: UUID, _ status: DownloadStatus) async {
guard let networkManager = networkManager else { return }
if let index = networkManager.downloadTasks.firstIndex(where: { $0.id == taskId }) {
networkManager.downloadTasks[index].setStatus(status)
switch status {
case .completed, .failed:
networkManager.progressObservers[taskId]?.invalidate()
networkManager.progressObservers.removeValue(forKey: taskId)
if networkManager.activeDownloadTaskId == taskId {
networkManager.activeDownloadTaskId = nil
}
case .downloading:
networkManager.activeDownloadTaskId = taskId
case .paused:
if networkManager.activeDownloadTaskId == taskId {
networkManager.activeDownloadTaskId = nil
}
default:
break
}
networkManager.updateDockBadge()
networkManager.objectWillChange.send()
}
}
}

View File

@@ -1,5 +1,5 @@
//
// Adobe-Downloader
// Adobe Downloader
//
// Created by X1a0He on 2024/10/30.
//
@@ -14,210 +14,56 @@ actor InstallManager {
var errorDescription: String? {
switch self {
case .setupNotFound:
return "找不到安装程序"
case .installationFailed(let message):
return message
case .cancelled:
return "安装已取消"
case .permissionDenied:
return "权限被拒绝"
case .setupNotFound: return "找不到安装程序"
case .installationFailed(let message): return message
case .cancelled: return "安装已取消"
case .permissionDenied: return "权限被拒绝"
}
}
}
private var installationProcess: Process?
private var progressHandler: ((Double, String) -> Void)?
private let setupPath = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup"
func install(at appPath: URL,
progressHandler: @escaping (Double, String) -> Void,
logHandler: @escaping (String) -> Void) async throws {
self.progressHandler = progressHandler
let setupPath = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup"
let driverPath = appPath.appendingPathComponent("Contents/Resources/products/driver.xml").path
private func executeInstallation(
at appPath: URL,
withSudo: Bool = true,
password: String? = nil,
progressHandler: @escaping (Double, String) -> Void,
logHandler: @escaping (String) -> Void
) async throws {
guard FileManager.default.fileExists(atPath: setupPath) else {
throw InstallError.setupNotFound
}
let authProcess = Process()
authProcess.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
let authScript = """
tell application "System Events"
display dialog "" default answer "" with hidden answer ¬
buttons {"", ""} default button "" ¬
with icon caution ¬
with title ""
if button returned of result is "" then
return text returned of result
else
error ""
end if
end tell
"""
let authPipe = Pipe()
authProcess.standardOutput = authPipe
authProcess.standardError = Pipe()
authProcess.arguments = ["-e", authScript]
do {
try authProcess.run()
authProcess.waitUntilExit()
if authProcess.terminationStatus != 0 {
throw InstallError.cancelled
}
guard let passwordData = try? authPipe.fileHandleForReading.readToEnd(),
let password = String(data: passwordData, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines),
!password.isEmpty else {
throw InstallError.cancelled
}
let installProcess = Process()
installProcess.executableURL = URL(fileURLWithPath: "/usr/bin/sudo")
installProcess.arguments = ["-S", setupPath, "--install=1", "--driverXML=\(driverPath)"]
print(installProcess)
let inputPipe = Pipe()
let outputPipe = Pipe()
installProcess.standardInput = inputPipe
installProcess.standardOutput = outputPipe
installProcess.standardError = outputPipe
installationProcess = installProcess
await MainActor.run {
progressHandler(0.0, "正在准备安装...")
}
try installProcess.run()
try inputPipe.fileHandleForWriting.write(contentsOf: "\(password)\n".data(using: .utf8)!)
inputPipe.fileHandleForWriting.closeFile()
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
Task.detached {
do {
for try await line in outputPipe.fileHandleForReading.bytes.lines {
await MainActor.run {
logHandler(line)
}
if line.contains("incorrect password") || line.contains("sudo: 1 incorrect password attempt") {
installProcess.terminate()
continuation.resume(throwing: InstallError.permissionDenied)
return
}
if let range = line.range(of: "Exit Code: (-?[0-9]+)", options: .regularExpression),
let codeStr = line[range].split(separator: ":").last?.trimmingCharacters(in: .whitespaces),
let code = Int32(codeStr) {
if code != 0 {
let errorMessage = code == -1
? "安装程序调用失败请联系X1a0He"
: "(退出代码: \(code))"
installProcess.terminate()
continuation.resume(throwing: InstallError.installationFailed(errorMessage))
return
}
}
if let progress = await self.parseProgress(from: line) {
await MainActor.run {
progressHandler(progress.progress, progress.status)
}
}
}
installProcess.waitUntilExit()
if installProcess.terminationStatus == 0 {
continuation.resume()
} else {
continuation.resume(throwing: InstallError.installationFailed(
"安装失败 (退出代码: \(installProcess.terminationStatus))"
))
}
} catch {
continuation.resume(throwing: error)
}
}
}
await MainActor.run {
progressHandler(1.0, "安装完成")
}
} catch {
if error.localizedDescription.contains("用户取消了操作") {
throw InstallError.cancelled
}
if let installError = error as? InstallError {
throw installError
}
throw InstallError.installationFailed(error.localizedDescription)
}
}
func cancel() {
installationProcess?.terminate()
}
private func parseProgress(from line: String) -> (progress: Double, status: String)? {
if let range = line.range(of: "Exit Code: (-?[0-9]+)", options: .regularExpression),
let codeStr = line[range].split(separator: ":").last?.trimmingCharacters(in: .whitespaces),
let exitCode = Int(codeStr) {
if exitCode == 0 {
return (1.0, "安装完成")
} else {
return nil
}
}
if let range = line.range(of: "Progress: ([0-9]{1,3})%", options: .regularExpression),
let progressStr = line[range].split(separator: ":").last?.trimmingCharacters(in: .whitespaces),
let progressValue = Double(progressStr.replacingOccurrences(of: "%", with: "")) {
return (progressValue / 100.0, "正在安装...")
}
if line.contains("Installing packages") {
return (0.0, "正在安装包...")
} else if line.contains("Preparing") {
return (0.0, "正在准备...")
}
return nil
}
func retry(at appPath: URL,
progressHandler: @escaping (Double, String) -> Void,
logHandler: @escaping (String) -> Void) async throws {
self.progressHandler = progressHandler
let setupPath = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup"
let driverPath = appPath.appendingPathComponent("Contents/Resources/products/driver.xml").path
guard FileManager.default.fileExists(atPath: setupPath) else {
throw InstallError.setupNotFound
}
let installProcess = Process()
installProcess.executableURL = URL(fileURLWithPath: "/usr/bin/sudo")
installProcess.arguments = [setupPath, "--install=1", "--driverXML=\(driverPath)"]
print("执行重试命令: \(installProcess.executableURL!.path) \(installProcess.arguments!.joined(separator: " "))")
if withSudo {
installProcess.executableURL = URL(fileURLWithPath: "/usr/bin/sudo")
installProcess.arguments = password != nil ? ["-S"] : []
installProcess.arguments?.append(contentsOf: [setupPath, "--install=1", "--driverXML=\(driverPath)"])
} else {
installProcess.executableURL = URL(fileURLWithPath: setupPath)
installProcess.arguments = ["--install=1", "--driverXML=\(driverPath)"]
}
let outputPipe = Pipe()
installProcess.standardOutput = outputPipe
installProcess.standardError = outputPipe
if let password = password {
let inputPipe = Pipe()
installProcess.standardInput = inputPipe
try inputPipe.fileHandleForWriting.write(contentsOf: "\(password)\n".data(using: .utf8)!)
inputPipe.fileHandleForWriting.closeFile()
}
installationProcess = installProcess
await MainActor.run {
progressHandler(0.0, "正在重试安装...")
progressHandler(0.0, withSudo ? "正在准备安装..." : "正在重试安装...")
}
try installProcess.run()
@@ -226,8 +72,12 @@ actor InstallManager {
Task.detached {
do {
for try await line in outputPipe.fileHandleForReading.bytes.lines {
await MainActor.run {
logHandler(line)
await MainActor.run { logHandler(line) }
if line.contains("incorrect password") || line.contains("sudo: 1 incorrect password attempt") {
installProcess.terminate()
continuation.resume(throwing: InstallError.permissionDenied)
return
}
if let range = line.range(of: "Exit Code: (-?[0-9]+)", options: .regularExpression),
@@ -256,9 +106,10 @@ actor InstallManager {
if installProcess.terminationStatus == 0 {
continuation.resume()
} else {
throw InstallError.installationFailed(
"重试失败,需要重新输入密码"
)
let errorMessage = withSudo
? "安装失败 (退出代码: \(installProcess.terminationStatus))"
: "重试失败,需要重新输入密码"
continuation.resume(throwing: InstallError.installationFailed(errorMessage))
}
} catch {
continuation.resume(throwing: error)
@@ -270,5 +121,100 @@ actor InstallManager {
progressHandler(1.0, "安装完成")
}
}
func install(
at appPath: URL,
progressHandler: @escaping (Double, String) -> Void,
logHandler: @escaping (String) -> Void
) async throws {
self.progressHandler = progressHandler
let password = try await requestPassword()
try await executeInstallation(
at: appPath,
withSudo: true,
password: password,
progressHandler: progressHandler,
logHandler: logHandler
)
}
func retry(
at appPath: URL,
progressHandler: @escaping (Double, String) -> Void,
logHandler: @escaping (String) -> Void
) async throws {
self.progressHandler = progressHandler
try await executeInstallation(
at: appPath,
withSudo: false,
progressHandler: progressHandler,
logHandler: logHandler
)
}
private func requestPassword() async throws -> String {
let authProcess = Process()
authProcess.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
let authScript = """
tell application "System Events"
display dialog "" default answer "" with hidden answer ¬
buttons {"", ""} default button "" ¬
with icon caution ¬
with title ""
if button returned of result is "" then
return text returned of result
else
error ""
end if
end tell
"""
let authPipe = Pipe()
authProcess.standardOutput = authPipe
authProcess.standardError = Pipe()
authProcess.arguments = ["-e", authScript]
try authProcess.run()
authProcess.waitUntilExit()
if authProcess.terminationStatus != 0 {
throw InstallError.cancelled
}
guard let passwordData = try? authPipe.fileHandleForReading.readToEnd(),
let password = String(data: passwordData, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines),
!password.isEmpty else {
throw InstallError.cancelled
}
return password
}
func cancel() {
installationProcess?.terminate()
}
private func parseProgress(from line: String) -> (progress: Double, status: String)? {
if let range = line.range(of: "Exit Code: (-?[0-9]+)", options: .regularExpression),
let codeStr = line[range].split(separator: ":").last?.trimmingCharacters(in: .whitespaces),
let exitCode = Int(codeStr) {
return exitCode == 0 ? (1.0, "安装完成") : nil
}
if let range = line.range(of: "Progress: ([0-9]{1,3})%", options: .regularExpression),
let progressStr = line[range].split(separator: ":").last?.trimmingCharacters(in: .whitespaces),
let progressValue = Double(progressStr.replacingOccurrences(of: "%", with: "")) {
return (progressValue / 100.0, "正在安装...")
}
if line.contains("Installing packages") {
return (0.0, "正在安装包...")
} else if line.contains("Preparing") {
return (0.0, "正在准备...")
}
return nil
}
}

View File

@@ -0,0 +1,85 @@
//
// ModifySetup.swift
// Adobe Downloader
//
// Created by X1a0He on 11/5/24.
//
import Foundation
import SwiftUI
class ModifySetup {
static func checkSetupBackup() -> Bool {
let setupPath = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup"
let backupPath = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup.original"
return FileManager.default.fileExists(atPath: setupPath) &&
!FileManager.default.fileExists(atPath: backupPath)
}
static func backupSetupFile(completion: @escaping (Bool, String) -> Void) {
let setupQueue = DispatchQueue(label: "com.x1a0he.adobedownloader.setup")
setupQueue.async {
let setupPath = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup"
let backupPath = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup.original"
let shellScript = """
#!/bin/bash
function hex() {
echo ''$1'' | perl -0777pe 's|([0-9a-zA-Z]{2}+(?![^\\(]*\\)))|\\\\x${1}|gs'
}
function replace() {
declare -r dom=$(hex "$2")
declare -r sub=$(hex "$3")
perl -0777pi -e 'BEGIN{$/=\\1e8} s|'$dom'|'$sub'|gs' "$1"
}
function prep() {
codesign --remove-signature "$1"
codesign -f -s - --timestamp=none --all-architectures --deep "$1"
xattr -cr "$1"
}
cp '\(setupPath)' '\(backupPath)'
# M 系列的修改暂时缺少Intel机型的特征码
replace '\(setupPath)' 'FFC300D1F44F01A9FD7B02A9FD830091F30300AA1F2003D568A11D58080140F9E80700F9' '200080D2C0035FD6FD7B02A9FD830091F30300AA1F2003D568A11D58080140F9E80700F9'
prep '\(setupPath)'
"""
let tempScriptPath = FileManager.default.temporaryDirectory.appendingPathComponent("setup_script.sh")
do {
try shellScript.write(to: tempScriptPath, atomically: true, encoding: .utf8)
let script = """
do shell script "chmod +x '\(tempScriptPath.path)' && sudo '\(tempScriptPath.path)'" with administrator privileges
"""
var scriptError: NSDictionary?
DispatchQueue.main.sync {
let appleScript = NSAppleScript(source: script)
scriptError = nil
_ = appleScript?.executeAndReturnError(&scriptError)
}
try? FileManager.default.removeItem(at: tempScriptPath)
DispatchQueue.main.async {
if let error = scriptError {
completion(false, "操作执行时发生错误: \(error)")
} else {
completion(true, "所有操作已成功完成")
}
}
} catch {
DispatchQueue.main.async {
completion(false, "创建脚本文件时发生错误: \(error)")
}
}
}
}
}

View File

@@ -1,5 +1,5 @@
//
// Adobe-Downloader
// Adobe Downloader
//
// Created by X1a0He on 2024/10/30.
//

View File

@@ -1,5 +1,5 @@
//
// Adobe-Downloader
// Adobe Downloader
//
// Created by X1a0He on 2024/10/30.
//
@@ -133,7 +133,12 @@ struct AboutAppView: View {
destination: URL(string: "https://github.com/Drovosek01/adobe-packager")!)
.font(.caption)
.foregroundColor(.blue)
Link("Thanks QiuChenly: InjectLib",
destination: URL(string: "https://github.com/QiuChenly/InjectLib")!)
.font(.caption)
.foregroundColor(.blue)
Text("Released under GPLv3.")
.font(.caption)
.foregroundColor(.secondary)

View File

@@ -1,5 +1,5 @@
//
// Adobe-Downloader
// Adobe Downloader
//
// Created by X1a0He on 2024/10/30.
//
@@ -8,50 +8,60 @@ import SwiftUI
class IconCache {
static let shared = IconCache()
private var cache: [String: NSImage] = [:]
private let queue = DispatchQueue(label: "com.adobe.downloader.iconcache")
private var cache = NSCache<NSString, NSImage>()
func getIcon(for url: String) -> NSImage? {
queue.sync {
return cache[url]
}
cache.object(forKey: url as NSString)
}
func setIcon(_ image: NSImage, for url: String) {
queue.sync {
self.cache[url] = image
}
cache.setObject(image, forKey: url as NSString)
}
}
struct AppCardView: View {
class AppCardViewModel: ObservableObject {
@Published var iconImage: NSImage?
@Published var showError = false
@Published var errorMessage = ""
@Published var showVersionPicker = false
@Published var selectedVersion = ""
@Published var showLanguagePicker = false
@Published var selectedLanguage = ""
@Published var showExistingFileAlert = false
@Published var existingFilePath: URL?
@Published var pendingVersion = ""
@Published var pendingLanguage = ""
@Published var showRedownloadConfirm = false
let sap: Sap
@EnvironmentObject private var networkManager: NetworkManager
@AppStorage("defaultDirectory") private var defaultDirectory: String = ""
@AppStorage("useDefaultDirectory") private var useDefaultDirectory: Bool = true
@AppStorage("useDefaultLanguage") private var useDefaultLanguage: Bool = true
@AppStorage("defaultLanguage") private var defaultLanguage: String = "zh_CN"
@AppStorage("confirmRedownload") private var confirmRedownload: Bool = true
@State private var showError: Bool = false
@State private var errorMessage: String = ""
@State private var showVersionPicker = false
@State private var selectedVersion: String = ""
@State private var iconImage: NSImage? = nil
@State private var showLanguagePicker = false
@State private var selectedLanguage = ""
@State private var showExistingFileAlert = false
@State private var existingFilePath: URL? = nil
@State private var pendingVersion: String = ""
@State private var pendingLanguage: String = ""
@State private var showRedownloadConfirm = false
private var isDownloading: Bool {
networkManager.downloadTasks.contains(where: isTaskDownloading)
weak var networkManager: NetworkManager?
@Published var isDownloading = false
private let userDefaults = UserDefaults.standard
var useDefaultDirectory: Bool {
get { userDefaults.bool(forKey: "useDefaultDirectory") }
}
var defaultDirectory: String {
get { userDefaults.string(forKey: "defaultDirectory") ?? "" }
}
init(sap: Sap, networkManager: NetworkManager?) {
self.sap = sap
self.networkManager = networkManager
loadIcon()
}
func updateDownloadingStatus() {
Task { @MainActor in
isDownloading = networkManager?.downloadTasks.contains(where: isTaskDownloading) ?? false
}
}
private func isTaskDownloading(_ task: NewDownloadTask) -> Bool {
guard task.sapCode == sap.sapCode else { return false }
switch task.totalStatus {
case .downloading, .preparing, .waiting, .retrying:
return true
@@ -60,51 +70,44 @@ struct AppCardView: View {
}
}
private var dependenciesCount: Int {
if let firstVersion = sap.versions.first?.value {
return firstVersion.dependencies.count
func getDestinationURL(version: String, language: String, useDefaultDirectory: Bool, defaultDirectory: String) async throws -> URL {
let platform = sap.versions[version]?.apPlatform ?? "unknown"
let installerName = sap.sapCode == "APRO"
? "Install \(sap.sapCode)_\(version)_\(platform).dmg"
: "Install \(sap.sapCode)_\(version)-\(language)-\(platform).app"
if useDefaultDirectory && !defaultDirectory.isEmpty {
return URL(fileURLWithPath: defaultDirectory)
.appendingPathComponent(installerName)
}
return try await withCheckedThrowingContinuation { continuation in
DispatchQueue.main.async {
let panel = NSOpenPanel()
panel.title = "选择保存位置"
panel.canCreateDirectories = true
panel.canChooseDirectories = true
panel.canChooseFiles = false
panel.directoryURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first
if panel.runModal() == .OK, let selectedURL = panel.url {
continuation.resume(returning: selectedURL.appendingPathComponent(installerName))
} else {
continuation.resume(throwing: NetworkError.cancelled)
}
}
}
return 0
}
var body: some View {
CardContent(
sap: sap,
iconImage: iconImage,
loadIcon: loadIcon,
dependenciesCount: dependenciesCount,
isDownloading: isDownloading,
showVersionPicker: $showVersionPicker
)
.padding()
.frame(width: 250, height: 200)
.background(RoundedRectangle(cornerRadius: 10).fill(Color.black.opacity(0.05)))
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray.opacity(0.1), lineWidth: 2)
)
.applyModifiers(
showVersionPicker: $showVersionPicker,
showLanguagePicker: $showLanguagePicker,
showExistingFileAlert: $showExistingFileAlert,
showError: $showError,
sap: sap,
existingFilePath: existingFilePath,
pendingVersion: pendingVersion,
pendingLanguage: pendingLanguage,
errorMessage: errorMessage,
selectedVersion: selectedVersion,
selectedLanguage: $selectedLanguage,
handleDownloadRequest: handleDownloadRequest,
checkAndStartDownload: checkAndStartDownload,
startDownload: startDownload,
createCompletedTask: createCompletedTask,
confirmRedownload: confirmRedownload,
showRedownloadConfirm: $showRedownloadConfirm
)
func handleError(_ error: Error) {
Task { @MainActor in
if case NetworkError.cancelled = error { return }
errorMessage = error.localizedDescription
showError = true
}
}
private func loadIcon() {
func loadIcon() {
if let bestIcon = sap.getBestIcon(),
let iconURL = URL(string: bestIcon.url) {
@@ -144,83 +147,61 @@ struct AppCardView: View {
}
}
private func handleDownloadRequest(_ version: String) {
if useDefaultLanguage {
checkAndStartDownload(version: version, language: defaultLanguage)
} else {
selectedVersion = version
showLanguagePicker = true
func handleDownloadRequest(_ version: String, useDefaultLanguage: Bool, defaultLanguage: String) async {
await MainActor.run {
if useDefaultLanguage {
Task {
await checkAndStartDownload(version: version, language: defaultLanguage)
}
} else {
selectedVersion = version
showLanguagePicker = true
}
}
}
private func checkAndStartDownload(version: String, language: String) {
if let existingPath = networkManager.isVersionDownloaded(sap: sap, version: version, language: language) {
existingFilePath = existingPath
pendingVersion = version
pendingLanguage = language
showExistingFileAlert = true
} else {
startDownload(version, language)
func checkAndStartDownload(version: String, language: String) async {
if let networkManager = networkManager {
if let existingPath = await networkManager.isVersionDownloaded(sap: sap, version: version, language: language) {
await MainActor.run {
existingFilePath = existingPath
pendingVersion = version
pendingLanguage = language
showExistingFileAlert = true
}
} else {
startDownload(version, language)
}
}
}
private func startDownload(_ version: String, _ language: String) {
func startDownload(_ version: String, _ language: String) {
Task {
do {
let destinationURL: URL
let platform = sap.versions[version]?.apPlatform ?? "unknown"
if useDefaultDirectory && !defaultDirectory.isEmpty {
var appName = ""
if(sap.sapCode=="APRO"){
appName = "Install \(sap.sapCode)_\(version)_\(platform).dmg"
} else {
appName = "Install \(sap.sapCode)_\(version)-\(language)-\(platform).app"
}
destinationURL = URL(fileURLWithPath: defaultDirectory)
.appendingPathComponent(appName)
} else {
let panel = NSOpenPanel()
panel.title = "选择保存位置"
panel.canCreateDirectories = true
panel.canChooseDirectories = true
panel.canChooseFiles = false
panel.directoryURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first
guard await MainActor.run(body: { panel.runModal() == .OK }),
let selectedURL = panel.url else {
return
}
var appName = ""
if(sap.sapCode=="APRO"){
appName = "Install \(sap.sapCode)_\(version)_\(platform).dmg"
} else {
appName = "Install \(sap.sapCode)_\(version)-\(language)-\(platform).app"
}
destinationURL = URL(fileURLWithPath: defaultDirectory)
.appendingPathComponent(appName)
}
try await networkManager.startDownload(
let destinationURL = try await getDestinationURL(
version: version,
language: language,
useDefaultDirectory: useDefaultDirectory,
defaultDirectory: defaultDirectory
)
try await networkManager?.startDownload(
sap: sap,
selectedVersion: version,
language: language,
destinationURL: destinationURL
)
} catch {
await MainActor.run {
errorMessage = error.localizedDescription
showError = true
}
handleError(error)
}
}
}
private func createCompletedTask(_ path: URL) {
guard let productInfo = sap.versions[pendingVersion] else { return }
func createCompletedTask(_ path: URL) async {
guard let networkManager = networkManager,
let productInfo = sap.versions[pendingVersion] else { return }
var productsToDownload: [ProductsToDownload] = []
let mainProduct = ProductsToDownload(
sapCode: sap.sapCode,
version: pendingVersion,
@@ -229,14 +210,14 @@ struct AppCardView: View {
productsToDownload.append(mainProduct)
for dependency in productInfo.dependencies {
if let dependencyVersions = networkManager.saps[dependency.sapCode]?.versions {
if let dependencyVersions = await networkManager.saps[dependency.sapCode]?.versions {
let sortedVersions = dependencyVersions.sorted { first, second in
first.value.productVersion.compare(second.value.productVersion, options: .numeric) == .orderedDescending
}
var buildGuid = ""
for (_, versionInfo) in sortedVersions where versionInfo.baseVersion == dependency.version {
if networkManager.allowedPlatform.contains(versionInfo.apPlatform) {
if await networkManager.allowedPlatform.contains(versionInfo.apPlatform) {
buildGuid = versionInfo.buildGuid
break
}
@@ -273,11 +254,67 @@ struct AppCardView: View {
totalSpeed: 0
)
Task { @MainActor in
await MainActor.run {
networkManager.downloadTasks.append(completedTask)
networkManager.objectWillChange.send()
}
}
var dependenciesCount: Int {
if let firstVersion = sap.versions.first?.value {
return firstVersion.dependencies.count
}
return 0
}
}
struct AppCardView: View {
@StateObject private var viewModel: AppCardViewModel
@EnvironmentObject private var networkManager: NetworkManager
@AppStorage("useDefaultLanguage") private var useDefaultLanguage = true
@AppStorage("defaultLanguage") private var defaultLanguage: String = "zh_CN"
init(sap: Sap) {
_viewModel = StateObject(wrappedValue: AppCardViewModel(sap: sap, networkManager: nil))
}
var body: some View {
CardContent(
sap: viewModel.sap,
iconImage: viewModel.iconImage,
loadIcon: viewModel.loadIcon,
dependenciesCount: viewModel.dependenciesCount,
isDownloading: viewModel.isDownloading,
showVersionPicker: $viewModel.showVersionPicker
)
.padding()
.frame(width: 250, height: 200)
.background(RoundedRectangle(cornerRadius: 10).fill(Color.black.opacity(0.05)))
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray.opacity(0.1), lineWidth: 2)
)
.modifier(AlertModifier(viewModel: viewModel, confirmRedownload: true))
.sheet(isPresented: $viewModel.showVersionPicker) {
VersionPickerView(sap: viewModel.sap) { version in
Task {
await viewModel.handleDownloadRequest(version, useDefaultLanguage: useDefaultLanguage, defaultLanguage: defaultLanguage)
}
}
.environmentObject(networkManager)
}
.sheet(isPresented: $viewModel.showLanguagePicker) {
LanguagePickerView(languages: AppStatics.supportedLanguages) { language in
Task {
await viewModel.checkAndStartDownload(version: viewModel.selectedVersion, language: language)
}
}
}
.onAppear {
viewModel.networkManager = networkManager
viewModel.updateDownloadingStatus()
}
}
}
private struct CardContent: View {
@@ -302,87 +339,8 @@ private struct CardContent: View {
}
private extension View {
func applyModifiers(
showVersionPicker: Binding<Bool>,
showLanguagePicker: Binding<Bool>,
showExistingFileAlert: Binding<Bool>,
showError: Binding<Bool>,
sap: Sap,
existingFilePath: URL?,
pendingVersion: String,
pendingLanguage: String,
errorMessage: String,
selectedVersion: String,
selectedLanguage: Binding<String>,
handleDownloadRequest: @escaping (String) -> Void,
checkAndStartDownload: @escaping (String, String) -> Void,
startDownload: @escaping (String, String) -> Void,
createCompletedTask: @escaping (URL) -> Void,
confirmRedownload: Bool,
showRedownloadConfirm: Binding<Bool>
) -> some View {
self
.sheet(isPresented: showVersionPicker) {
VersionPickerView(sap: sap, onSelect: handleDownloadRequest)
}
.sheet(isPresented: showLanguagePicker) {
LanguagePickerView(languages: AppStatics.supportedLanguages) { language in
selectedLanguage.wrappedValue = language
showLanguagePicker.wrappedValue = false
if !selectedVersion.isEmpty {
checkAndStartDownload(selectedVersion, language)
}
}
}
.alert("安装程序已存在", isPresented: showExistingFileAlert) {
Button("使用现有程序") {
if let path = existingFilePath,
!pendingVersion.isEmpty && !pendingLanguage.isEmpty {
createCompletedTask(path)
}
}
Button("重新下载") {
if !pendingVersion.isEmpty && !pendingLanguage.isEmpty {
if confirmRedownload {
showRedownloadConfirm.wrappedValue = true
} else {
startDownload(pendingVersion, pendingLanguage)
}
}
}
Button("取消", role: .cancel) {}
} message: {
VStack(alignment: .leading) {
Text("在以下位置找到现有的安装程序:")
if let path = existingFilePath {
Text(path.path)
.foregroundColor(.blue)
.onTapGesture {
NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path)
}
}
}
}
.alert("确认重新下载", isPresented: showRedownloadConfirm) {
Button("取消", role: .cancel) { }
Button("确认") {
if !pendingVersion.isEmpty && !pendingLanguage.isEmpty {
startDownload(pendingVersion, pendingLanguage)
}
}
} message: {
Text("是否确认重新下载?这将覆盖现有的安装程序。")
}
.alert("下载错误", isPresented: showError) {
Button("确定", role: .cancel) { }
Button("重试") {
if !selectedVersion.isEmpty {
startDownload(selectedVersion, selectedLanguage.wrappedValue)
}
}
} message: {
Text(errorMessage)
}
func applyModifiers(viewModel: AppCardViewModel) -> some View {
self.modifier(AlertModifier(viewModel: viewModel, confirmRedownload: true))
}
}
@@ -452,7 +410,8 @@ private struct DownloadButton: View {
}
#Preview {
AppCardView(sap: Sap(
let networkManager = NetworkManager()
let sap = Sap(
hidden: false,
displayName: "Photoshop",
sapCode: "PHSP",
@@ -476,6 +435,68 @@ private struct DownloadButton: View {
url: "https://ffc-static-cdn.oobesaas.adobe.com/icons/PHSP/25.0.0/192x192.png"
)
]
))
.environmentObject(NetworkManager())
)
return AppCardView(sap: sap)
.environmentObject(networkManager)
}
struct AlertModifier: ViewModifier {
@ObservedObject var viewModel: AppCardViewModel
let confirmRedownload: Bool
func body(content: Content) -> some View {
content
.alert("安装程序已存在", isPresented: $viewModel.showExistingFileAlert) {
Button("使用现有程序") {
if let path = viewModel.existingFilePath,
!viewModel.pendingVersion.isEmpty && !viewModel.pendingLanguage.isEmpty {
Task {
await viewModel.createCompletedTask(path)
}
}
}
Button("重新下载") {
if !viewModel.pendingVersion.isEmpty && !viewModel.pendingLanguage.isEmpty {
if confirmRedownload {
viewModel.showRedownloadConfirm = true
} else {
viewModel.startDownload(viewModel.pendingVersion, viewModel.pendingLanguage)
}
}
}
Button("取消", role: .cancel) {}
} message: {
VStack(alignment: .leading) {
Text("在以下位置找到现有的安装程序:")
if let path = viewModel.existingFilePath {
Text(path.path)
.foregroundColor(.blue)
.onTapGesture {
NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path)
}
}
}
}
.alert("确认重新下载", isPresented: $viewModel.showRedownloadConfirm) {
Button("取消", role: .cancel) { }
Button("确认") {
if !viewModel.pendingVersion.isEmpty && !viewModel.pendingLanguage.isEmpty {
viewModel.startDownload(viewModel.pendingVersion, viewModel.pendingLanguage)
}
}
} message: {
Text("是否确认重新下载?这将覆盖现有的安装程序。")
}
.alert("下载错误", isPresented: $viewModel.showError) {
Button("确定", role: .cancel) { }
Button("重试") {
if !viewModel.selectedVersion.isEmpty {
viewModel.startDownload(viewModel.selectedVersion, viewModel.selectedLanguage)
}
}
} message: {
Text(viewModel.errorMessage)
}
}
}

View File

@@ -1,5 +1,5 @@
//
// Adobe-Downloader
// Adobe Downloader
//
// Created by X1a0He on 2024/10/30.
//
@@ -27,8 +27,7 @@ struct DownloadManagerView: View {
}
private func removeTask(_ task: NewDownloadTask) {
networkManager.downloadTasks.removeAll { $0.id == task.id }
networkManager.updateDockBadge()
networkManager.removeTask(taskId: task.id)
}
private func sortTasks(_ tasks: [NewDownloadTask]) -> [NewDownloadTask] {
@@ -47,35 +46,60 @@ struct DownloadManagerView: View {
}
var body: some View {
VStack(spacing: 12) {
VStack(spacing: 0) {
HStack {
Text("下载管理")
.font(.headline)
Spacer()
Menu {
ForEach([SortOrder.addTime, .name, .status], id: \.self) { order in
Button(action: {
sortOrder = order
}) {
HStack {
Text(order.description)
if sortOrder == order {
Image(systemName: "checkmark")
HStack(){
Menu {
ForEach([SortOrder.addTime, .name, .status], id: \.self) { order in
Button(action: {
sortOrder = order
}) {
HStack {
Text(order.description)
if sortOrder == order {
Image(systemName: "checkmark")
}
}
}
}
}
} label: {
HStack {
Image(systemName: "arrow.up.arrow.down")
Text(sortOrder.description)
.font(.caption)
} label: {
HStack {
Image(systemName: "arrow.up.arrow.down")
Text(sortOrder.description)
.font(.caption)
}
}
}
.frame(minWidth: 120)
.fixedSize()
Button("全部暂停", action: {})
Button("全部继续", action: {})
Button("清理已完成", action: {
Button("全部暂停") {
Task {
for task in networkManager.downloadTasks {
if case .downloading = task.status {
await networkManager.downloadUtils.pauseDownloadTask(
taskId: task.id,
reason: .userRequested
)
}
}
}
}
Button("全部继续") {
Task {
for task in networkManager.downloadTasks {
if case .paused = task.status {
await networkManager.downloadUtils.resumeDownloadTask(taskId: task.id)
}
}
}
}
Button("清理已完成") {
networkManager.downloadTasks.removeAll { task in
if case .completed = task.status {
return true
@@ -83,13 +107,14 @@ struct DownloadManagerView: View {
return false
}
networkManager.updateDockBadge()
})
}
Button("关闭") {
dismiss()
}
}
.padding()
.padding(.horizontal)
.padding(.vertical, 8)
ScrollView(showsIndicators: false) {
LazyVStack(spacing: 8) {
@@ -97,19 +122,26 @@ struct DownloadManagerView: View {
DownloadProgressView(
task: task,
onCancel: {
networkManager.cancelDownload(taskId: task.id)
Task {
await networkManager.downloadUtils.cancelDownloadTask(taskId: task.id)
}
},
onPause: {
networkManager.pauseDownload(taskId: task.id)
Task {
await networkManager.downloadUtils.pauseDownloadTask(
taskId: task.id,
reason: .userRequested
)
}
},
onResume: {
Task {
await networkManager.resumeDownload(taskId: task.id)
await networkManager.downloadUtils.resumeDownloadTask(taskId: task.id)
}
},
onRetry: {
Task {
await networkManager.resumeDownload(taskId: task.id)
await networkManager.downloadUtils.resumeDownloadTask(taskId: task.id)
}
},
onRemove: {
@@ -119,11 +151,11 @@ struct DownloadManagerView: View {
}
}
.padding(.horizontal)
.padding(.bottom, 12)
.padding(.vertical, 8)
}
.background(Color(NSColor.windowBackgroundColor))
}
.frame(width: 600, height: 500)
.frame(width:800, height: 600)
}
}

View File

@@ -1,5 +1,5 @@
//
// Adobe-Downloader
// Adobe Downloader
//
// Created by X1a0He on 2024/10/30.
//
@@ -305,7 +305,7 @@ struct DownloadProgressView: View {
}
let lastComponents = components.suffix(2)
return ".../" + lastComponents.joined(separator: "/")
return "/" + lastComponents.joined(separator: "/")
}
var body: some View {
@@ -499,6 +499,35 @@ struct ProductRow: View {
struct PackageRow: View {
@ObservedObject var package: Package
private func statusView() -> some View {
Group {
switch package.status {
case .waiting:
HStack {
Image(systemName: "hourglass.circle.fill")
Text(package.status.description)
}
.foregroundColor(.secondary)
case .downloading:
HStack {
Text("\(Int(package.progress * 100))%")
}
.foregroundColor(.blue)
case .completed:
HStack {
Image(systemName: "checkmark.circle.fill")
Text(package.status.description)
}
.foregroundColor(.green)
default:
HStack {
Text(package.status.description)
}
.foregroundColor(.secondary)
}
}
}
private func formatSpeed(_ bytesPerSecond: Double) -> String {
let formatter = ByteCountFormatter()
formatter.countStyle = .file
@@ -529,24 +558,8 @@ struct PackageRow: View {
Spacer()
if package.status == .downloading {
Text("\(Int(package.progress * 100))%")
.font(.caption)
} else {
if package.status == .waiting {
Text("\(Image(systemName: "hourglass.circle.fill")) \(package.status.description)")
.font(.caption)
.foregroundColor(.secondary)
} else if package.status == .completed {
Text("\(Image(systemName: "checkmark.circle.fill")) \(package.status.description)")
.font(.caption)
.foregroundColor(.green)
} else {
Text("\(package.status.description)")
.font(.caption)
.foregroundColor(.secondary)
}
}
statusView()
.font(.caption)
}
if package.status == .downloading {

View File

@@ -1,5 +1,5 @@
//
// Adobe-Downloader
// Adobe Downloader
//
// Created by X1a0He on 2024/10/30.
//
@@ -119,7 +119,7 @@ private struct LogSection: View {
var body: some View {
ScrollViewReader { proxy in
ScrollView {
ScrollView(showsIndicators: false) {
VStack(alignment: .leading, spacing: 2) {
ForEach(Array(logs.enumerated()), id: \.offset) { index, log in
Text(log)

View File

@@ -1,5 +1,5 @@
//
// Adobe-Downloader
// Adobe Downloader
//
// Created by X1a0He on 2024/10/30.
//
@@ -9,8 +9,8 @@ struct LanguagePickerView: View {
let languages: [(code: String, name: String)]
let onLanguageSelected: (String) -> Void
@Environment(\.dismiss) private var dismiss
@AppStorage("defaultLanguage") private var defaultLanguage: String = "zh_CN"
@State private var searchText: String = ""
@State private var selectedLanguage: String = ""
private var filteredLanguages: [(code: String, name: String)] {
guard !searchText.isEmpty else {
@@ -64,9 +64,9 @@ struct LanguagePickerView: View {
ForEach(Array(filteredLanguages.enumerated()), id: \.element.code) { index, language in
LanguageRow(
language: language,
isSelected: language.code == defaultLanguage,
isSelected: language.code == selectedLanguage,
onSelect: {
defaultLanguage = language.code
selectedLanguage = language.code
onLanguageSelected(language.code)
dismiss()
}

View File

@@ -1,5 +1,5 @@
//
// Adobe-Downloader
// Adobe Downloader
//
// Created by X1a0He on 2024/10/30.
//
@@ -17,9 +17,9 @@ struct SettingsView: View {
private let languageMap: [(code: String, name: String)] = AppStatics.supportedLanguages
var body: some View {
VStack(spacing: 12) {
HStack(spacing: 12) {
HStack(spacing: 4) {
VStack() {
HStack() {
HStack() {
Toggle(isOn: $useDefaultLanguage) {
Text("语言:")
.fixedSize()
@@ -30,17 +30,16 @@ struct SettingsView: View {
Text(getLanguageName(code: defaultLanguage))
.foregroundColor(.secondary)
.lineLimit(1)
.frame(width: 80, alignment: .leading)
.frame(alignment: .leading)
Spacer()
Button("选择", action: onSelectLanguage)
.buttonStyle(.borderless)
.fixedSize()
}
Divider()
.frame(height: 16)
HStack(spacing: 4) {
HStack() {
Toggle(isOn: $useDefaultDirectory) {
Text("目录:")
.fixedSize()
@@ -51,10 +50,9 @@ struct SettingsView: View {
Text(formatPath(defaultDirectory.isEmpty ? "未设置" : defaultDirectory))
.foregroundColor(.secondary)
.lineLimit(1)
.frame(width: 120, alignment: .leading)
.frame(alignment: .leading)
Spacer()
Button("选择", action: onSelectDirectory)
.buttonStyle(.borderless)
.fixedSize()
}
}

View File

@@ -1,5 +1,5 @@
//
// Adobe-Downloader
// Adobe Downloader
//
// Created by X1a0He on 2024/10/30.
//

View File

@@ -33,22 +33,6 @@
}
}
}
},
"%@ %@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "%1$@ %2$@"
}
}
}
},
"%@ 安装失败" : {
},
"%@ 安装完成" : {
},
"%lld" : {
@@ -104,205 +88,643 @@
},
"Github: Adobe Downloader" : {
},
"OK" : {
},
"Released under GPLv3." : {
},
"Setup未备份提示" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Setup not backed up"
}
}
}
},
"Thanks Drovosek01: adobe-packager" : {
},
"Thanks QiuChenly: InjectLib" : {
},
"Welcome to Adobe Downloader" : {
},
"下载" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Download"
}
}
}
},
"下载中" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Downloading"
}
}
}
},
"下载已取消" : {
"comment" : "Download cancelled"
"comment" : "Download cancelled",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Download cancelled"
}
}
}
},
"下载此版本" : {
"comment" : "版本选择页面",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Download"
}
}
}
},
"下载管理" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Download management"
}
}
}
},
"下载设置" : {
"comment" : "设置页面",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Settings"
}
}
}
},
"下载错误" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Download error"
}
}
}
},
"产品和包列表" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Products and Packages"
}
}
}
},
"使用现有程序" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Use existing"
}
}
}
},
"使用默认目录" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Default directory"
}
}
}
},
"使用默认语言" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Default language"
}
}
}
},
"依赖包:" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Dependencies"
}
}
}
},
"依赖包: %lld" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Dependencies %lld"
}
}
}
},
"全部暂停" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pause All"
}
}
}
},
"全部继续" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Continue All"
}
}
}
},
"关闭" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Close"
}
}
}
},
"删除" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Delete"
}
}
}
},
"加载失败" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Loading failed"
}
}
}
},
"取消" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cancel"
}
}
}
},
"可用版本: %lld" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Availables: %lld"
}
}
}
},
"在以下位置找到现有的安装程序:" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Locate the existing installer at:"
}
}
}
},
"失败: %@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Failed: %@"
}
}
}
},
"安装" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Install"
}
}
}
},
"安装程序已存在" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Installer already exists"
}
}
}
},
"尝试使用不同的搜索关键词" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "try using different keywords"
}
}
}
},
"尝试其他搜索关键词" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "try other search keywords"
}
}
}
},
"已存在" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Existed"
}
}
}
},
"已完成" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Completed"
}
}
}
},
"已暂停" : {
"comment" : "Download status paused"
"comment" : "Download status paused",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Paused"
}
}
}
},
"搜索应用" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search"
}
}
}
},
"搜索语言" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "search language"
}
}
}
},
"是否确认重新下载?这将覆盖现有的安装程序。" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Are you sure you want to re-download? This will overwrite the existing installer"
}
}
}
},
"是否要安装 %@?" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Do you want to install %@"
}
}
}
},
"暂停" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pause"
}
}
}
},
"服务器响应无效" : {
"comment" : "Invalid response"
"comment" : "Invalid response",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Invalid server response"
}
}
}
},
"未找到语言" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "language not found"
}
}
}
},
"检测到Setup文件尚未备份如果你需要安装程序则Setup必须被处理点击确定后你需要输入密码Adobe Downloader将自动处理并备份为Setup.original" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "It is detected that the Setup file has not been backed up. If you need to install the program, the Setup must be processed. After clicking OK, you need to enter the password. Adobe Downloader will automatically process and back up the Setup.original."
}
}
}
},
"正在下载 %@ (%d/%d)" : {
"comment" : "Download status downloading",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "正在下载 %1$@ (%2$d/%3$d)"
"state" : "translated",
"value" : "Downloading %1$@ (%2$d/%3$d)"
}
}
}
},
"正在加载..." : {
},
"正在安装 %@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Loading…"
}
}
}
},
"没有找到产品" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "No products found"
}
}
}
},
"没有网络连接" : {
"comment" : "Network error"
"comment" : "Network error",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Network error"
}
}
}
},
"清理已完成" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cleanup Completed"
}
}
}
},
"目录:" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Directory: "
}
}
}
},
"确定" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Confirm"
}
}
}
},
"确认" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Confirm"
}
}
}
},
"确认重新下载" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Confirm re-download"
}
}
}
},
"移除" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Delete"
}
}
}
},
"等待中" : {
"comment" : "Download status waiting"
"comment" : "Download status waiting",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Waiting…"
}
}
}
},
"系统休眠" : {
"comment" : "Download status system sleep"
"comment" : "Download status system sleep",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "System hibernation"
}
}
}
},
"继续" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Continue"
}
}
}
},
"网络中断" : {
"comment" : "Download status network paused"
"comment" : "Download status network paused",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Network interrupt"
}
}
}
},
"语言:" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Language: "
}
}
}
},
"选择" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Select"
}
}
}
},
"选择安装语言" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Select the installation language"
}
}
}
},
"选择版本" : {
"comment" : "版本选择页面",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Versions"
}
}
}
},
"重新下载" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Re-download"
}
}
}
},
"重新下载时需要确认" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Re-download for second confirmation"
}
}
}
},
"重试" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "retry"
}
}
}
},
"重试中 (%d/%d)" : {
"comment" : "Download status retrying",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "重试中 (%1$d/%2$d)"
"state" : "translated",
"value" : "Retrying (%1$d/%2$d)"
}
}
}
},
"错误详情:" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Error details:"
}
}
}
}
},
"version" : "1.0"

BIN
imgs/1024pt-1.png Normal file

Binary file not shown.

BIN
imgs/Adobe Downloader.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 MiB

BIN
imgs/download.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Some files were not shown because too many files have changed in this diff Show More