mirror of
https://github.com/TuringSoftware/CrystalFetch.git
synced 2024-09-20 06:55:58 +08:00
project: initial commit
This commit is contained in:
commit
fa7be1b985
70
.gitignore
vendored
Normal file
70
.gitignore
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
# Xcode
|
||||
#
|
||||
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
|
||||
|
||||
## User settings
|
||||
xcuserdata/
|
||||
|
||||
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
|
||||
*.xcscmblueprint
|
||||
*.xccheckout
|
||||
|
||||
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
|
||||
build/
|
||||
DerivedData/
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
|
||||
## App packaging
|
||||
*.ipa
|
||||
*.dSYM.zip
|
||||
*.dSYM
|
||||
|
||||
# CocoaPods
|
||||
#
|
||||
# We recommend against adding the Pods directory to your .gitignore. However
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||
#
|
||||
# Pods/
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from the Xcode workspace
|
||||
# *.xcworkspace
|
||||
|
||||
# Carthage
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
||||
# Carthage/Checkouts
|
||||
|
||||
Carthage/Build/
|
||||
|
||||
# fastlane
|
||||
#
|
||||
# It is recommended to not store the screenshots in the git repo.
|
||||
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/#source-control
|
||||
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots/**/*.png
|
||||
fastlane/test_output
|
||||
|
||||
# Code Injection
|
||||
#
|
||||
# After new code Injection tools there's a generated folder /iOSInjectionProject
|
||||
# https://github.com/johnno1962/injectionforxcode
|
||||
|
||||
iOSInjectionProject/
|
||||
|
||||
.DS_Store
|
384
Glass ISO Builder.xcodeproj/project.pbxproj
Normal file
384
Glass ISO Builder.xcodeproj/project.pbxproj
Normal file
|
@ -0,0 +1,384 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 56;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
CEC09F0F2A6BB66200980857 /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC09F0E2A6BB66200980857 /* Main.swift */; };
|
||||
CEC09F112A6BB66200980857 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC09F102A6BB66200980857 /* ContentView.swift */; };
|
||||
CEC09F132A6BB66300980857 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CEC09F122A6BB66300980857 /* Assets.xcassets */; };
|
||||
CEC09F262A6DAB7E00980857 /* BuildConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC09F252A6DAB7E00980857 /* BuildConfigView.swift */; };
|
||||
CEC09F282A6DBED400980857 /* Worker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC09F272A6DBED400980857 /* Worker.swift */; };
|
||||
CEC09F2B2A6DC40900980857 /* UUPBuilds.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC09F2A2A6DC40900980857 /* UUPBuilds.swift */; };
|
||||
CEC09F2D2A6DC60500980857 /* UUPDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC09F2C2A6DC60500980857 /* UUPDetails.swift */; };
|
||||
CEC09F2F2A6DC72000980857 /* UUPEditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC09F2E2A6DC72000980857 /* UUPEditions.swift */; };
|
||||
CEC09F312A6DC8FB00980857 /* UUPPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC09F302A6DC8FB00980857 /* UUPPackage.swift */; };
|
||||
CEC09F332A6DCA2C00980857 /* UUPDumpAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC09F322A6DCA2C00980857 /* UUPDumpAPI.swift */; };
|
||||
CEC09F352A6DF33C00980857 /* BuildsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC09F342A6DF33C00980857 /* BuildsListView.swift */; };
|
||||
CEC09F372A6E5B7600980857 /* BuildDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC09F362A6E5B7600980857 /* BuildDetails.swift */; };
|
||||
CEC09F392A6E5D5C00980857 /* BuildEditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC09F382A6E5D5C00980857 /* BuildEditions.swift */; };
|
||||
CEC09F3B2A6E629F00980857 /* PrettyString.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC09F3A2A6E629F00980857 /* PrettyString.swift */; };
|
||||
CEC09F3D2A6EECC700980857 /* Downloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC09F3C2A6EECC700980857 /* Downloader.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
CEC09F0B2A6BB66200980857 /* Glass ISO Builder.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Glass ISO Builder.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
CEC09F0E2A6BB66200980857 /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Main.swift; sourceTree = "<group>"; };
|
||||
CEC09F102A6BB66200980857 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
CEC09F122A6BB66300980857 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
CEC09F172A6BB66300980857 /* Glass_ISO_Builder.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Glass_ISO_Builder.entitlements; sourceTree = "<group>"; };
|
||||
CEC09F252A6DAB7E00980857 /* BuildConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildConfigView.swift; sourceTree = "<group>"; };
|
||||
CEC09F272A6DBED400980857 /* Worker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Worker.swift; sourceTree = "<group>"; };
|
||||
CEC09F2A2A6DC40900980857 /* UUPBuilds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UUPBuilds.swift; sourceTree = "<group>"; };
|
||||
CEC09F2C2A6DC60500980857 /* UUPDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UUPDetails.swift; sourceTree = "<group>"; };
|
||||
CEC09F2E2A6DC72000980857 /* UUPEditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UUPEditions.swift; sourceTree = "<group>"; };
|
||||
CEC09F302A6DC8FB00980857 /* UUPPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UUPPackage.swift; sourceTree = "<group>"; };
|
||||
CEC09F322A6DCA2C00980857 /* UUPDumpAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UUPDumpAPI.swift; sourceTree = "<group>"; };
|
||||
CEC09F342A6DF33C00980857 /* BuildsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildsListView.swift; sourceTree = "<group>"; };
|
||||
CEC09F362A6E5B7600980857 /* BuildDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildDetails.swift; sourceTree = "<group>"; };
|
||||
CEC09F382A6E5D5C00980857 /* BuildEditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildEditions.swift; sourceTree = "<group>"; };
|
||||
CEC09F3A2A6E629F00980857 /* PrettyString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrettyString.swift; sourceTree = "<group>"; };
|
||||
CEC09F3C2A6EECC700980857 /* Downloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Downloader.swift; sourceTree = "<group>"; };
|
||||
CEC09F3E2A6F151800980857 /* Glass-ISO-Builder-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Glass-ISO-Builder-Info.plist"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
CEC09F082A6BB66200980857 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
CEC09F022A6BB66200980857 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CEC09F0D2A6BB66200980857 /* Source */,
|
||||
CEC09F0C2A6BB66200980857 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CEC09F0C2A6BB66200980857 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CEC09F0B2A6BB66200980857 /* Glass ISO Builder.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CEC09F0D2A6BB66200980857 /* Source */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CEC09F292A6DC37800980857 /* UUPDump */,
|
||||
CEC09F0E2A6BB66200980857 /* Main.swift */,
|
||||
CEC09F102A6BB66200980857 /* ContentView.swift */,
|
||||
CEC09F3C2A6EECC700980857 /* Downloader.swift */,
|
||||
CEC09F252A6DAB7E00980857 /* BuildConfigView.swift */,
|
||||
CEC09F362A6E5B7600980857 /* BuildDetails.swift */,
|
||||
CEC09F382A6E5D5C00980857 /* BuildEditions.swift */,
|
||||
CEC09F342A6DF33C00980857 /* BuildsListView.swift */,
|
||||
CEC09F272A6DBED400980857 /* Worker.swift */,
|
||||
CEC09F122A6BB66300980857 /* Assets.xcassets */,
|
||||
CEC09F172A6BB66300980857 /* Glass_ISO_Builder.entitlements */,
|
||||
CEC09F3E2A6F151800980857 /* Glass-ISO-Builder-Info.plist */,
|
||||
);
|
||||
path = Source;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CEC09F292A6DC37800980857 /* UUPDump */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CEC09F2A2A6DC40900980857 /* UUPBuilds.swift */,
|
||||
CEC09F2C2A6DC60500980857 /* UUPDetails.swift */,
|
||||
CEC09F322A6DCA2C00980857 /* UUPDumpAPI.swift */,
|
||||
CEC09F2E2A6DC72000980857 /* UUPEditions.swift */,
|
||||
CEC09F302A6DC8FB00980857 /* UUPPackage.swift */,
|
||||
CEC09F3A2A6E629F00980857 /* PrettyString.swift */,
|
||||
);
|
||||
path = UUPDump;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
CEC09F0A2A6BB66200980857 /* Glass ISO Builder */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = CEC09F1A2A6BB66300980857 /* Build configuration list for PBXNativeTarget "Glass ISO Builder" */;
|
||||
buildPhases = (
|
||||
CEC09F072A6BB66200980857 /* Sources */,
|
||||
CEC09F082A6BB66200980857 /* Frameworks */,
|
||||
CEC09F092A6BB66200980857 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Glass ISO Builder";
|
||||
productName = "Glass ISO Builder";
|
||||
productReference = CEC09F0B2A6BB66200980857 /* Glass ISO Builder.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
CEC09F032A6BB66200980857 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1430;
|
||||
LastUpgradeCheck = 1430;
|
||||
ORGANIZATIONNAME = "Turing Software, LLC";
|
||||
TargetAttributes = {
|
||||
CEC09F0A2A6BB66200980857 = {
|
||||
CreatedOnToolsVersion = 14.3.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = CEC09F062A6BB66200980857 /* Build configuration list for PBXProject "Glass ISO Builder" */;
|
||||
compatibilityVersion = "Xcode 14.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = CEC09F022A6BB66200980857;
|
||||
productRefGroup = CEC09F0C2A6BB66200980857 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
CEC09F0A2A6BB66200980857 /* Glass ISO Builder */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
CEC09F092A6BB66200980857 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
CEC09F132A6BB66300980857 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
CEC09F072A6BB66200980857 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
CEC09F112A6BB66200980857 /* ContentView.swift in Sources */,
|
||||
CEC09F312A6DC8FB00980857 /* UUPPackage.swift in Sources */,
|
||||
CEC09F332A6DCA2C00980857 /* UUPDumpAPI.swift in Sources */,
|
||||
CEC09F282A6DBED400980857 /* Worker.swift in Sources */,
|
||||
CEC09F262A6DAB7E00980857 /* BuildConfigView.swift in Sources */,
|
||||
CEC09F0F2A6BB66200980857 /* Main.swift in Sources */,
|
||||
CEC09F372A6E5B7600980857 /* BuildDetails.swift in Sources */,
|
||||
CEC09F3B2A6E629F00980857 /* PrettyString.swift in Sources */,
|
||||
CEC09F3D2A6EECC700980857 /* Downloader.swift in Sources */,
|
||||
CEC09F2D2A6DC60500980857 /* UUPDetails.swift in Sources */,
|
||||
CEC09F352A6DF33C00980857 /* BuildsListView.swift in Sources */,
|
||||
CEC09F392A6E5D5C00980857 /* BuildEditions.swift in Sources */,
|
||||
CEC09F2F2A6DC72000980857 /* UUPEditions.swift in Sources */,
|
||||
CEC09F2B2A6DC40900980857 /* UUPBuilds.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
CEC09F182A6BB66300980857 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
CEC09F192A6BB66300980857 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
CEC09F1B2A6BB66300980857 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = Source/Glass_ISO_Builder.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "Source/Glass-ISO-Builder-Info.plist";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "llc.turing.Glass-ISO-Builder";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
CEC09F1C2A6BB66300980857 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = Source/Glass_ISO_Builder.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "Source/Glass-ISO-Builder-Info.plist";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "llc.turing.Glass-ISO-Builder";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
CEC09F062A6BB66200980857 /* Build configuration list for PBXProject "Glass ISO Builder" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CEC09F182A6BB66300980857 /* Debug */,
|
||||
CEC09F192A6BB66300980857 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
CEC09F1A2A6BB66300980857 /* Build configuration list for PBXNativeTarget "Glass ISO Builder" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CEC09F1B2A6BB66300980857 /* Debug */,
|
||||
CEC09F1C2A6BB66300980857 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = CEC09F032A6BB66200980857 /* Project object */;
|
||||
}
|
7
Glass ISO Builder.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
Glass ISO Builder.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>FILEHEADER</key>
|
||||
<string>
|
||||
// ___COPYRIGHT___
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//</string>
|
||||
</dict>
|
||||
</plist>
|
202
LICENSE
Normal file
202
LICENSE
Normal file
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
11
Source/Assets.xcassets/AccentColor.colorset/Contents.json
Normal file
11
Source/Assets.xcassets/AccentColor.colorset/Contents.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
58
Source/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
58
Source/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
6
Source/Assets.xcassets/Contents.json
Normal file
6
Source/Assets.xcassets/Contents.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
132
Source/BuildConfigView.swift
Normal file
132
Source/BuildConfigView.swift
Normal file
|
@ -0,0 +1,132 @@
|
|||
//
|
||||
// Copyright © 2023 Turing Software, LLC. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct BuildConfigView: View {
|
||||
let build: UUPBuilds.Build
|
||||
|
||||
@EnvironmentObject private var worker: Worker
|
||||
@AppStorage("LastSelectedLocale") private var lastSelectedLocale: String?
|
||||
@State private var selectedLocale: String = ""
|
||||
@State private var selectedEditions = Set<String>()
|
||||
@State private var isTermsAgreed: Bool = false
|
||||
@State private var isConfirmCancelShown: Bool = false
|
||||
@State private var details = BuildDetails.empty
|
||||
@State private var edition = BuildEditions.empty
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
ScrollView {
|
||||
Form {
|
||||
Section {
|
||||
HStack {
|
||||
Label("Channel", systemImage: "shippingbox")
|
||||
Spacer()
|
||||
Text(details.ring.prettyRing)
|
||||
}
|
||||
HStack {
|
||||
Label("Build", systemImage: "number")
|
||||
Spacer()
|
||||
Text(details.build)
|
||||
}
|
||||
HStack {
|
||||
Label("Created", systemImage: "calendar")
|
||||
Spacer()
|
||||
Text(DateFormatter.localizedString(from: details.created, dateStyle: .long, timeStyle: .short))
|
||||
}
|
||||
}.padding(.bottom, 5)
|
||||
Section("Language") {
|
||||
Picker("", selection: $selectedLocale) {
|
||||
ForEach(details.languages) { language in
|
||||
Text(language.display).tag(language.code)
|
||||
}
|
||||
}.onChange(of: selectedLocale) { newValue in
|
||||
worker.refreshDetails(uuid: build.uuid, language: newValue) {
|
||||
details = $0
|
||||
edition = $1
|
||||
selectedEditions.removeAll()
|
||||
selectedEditions.formUnion(edition.editions.map({ $0.code }))
|
||||
lastSelectedLocale = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
Section("Editions") {
|
||||
ForEach(edition.editions) { edition in
|
||||
Toggle(edition.display, isOn: Binding<Bool>(get: {
|
||||
selectedEditions.contains(edition.code)
|
||||
}, set: { newValue in
|
||||
if newValue {
|
||||
selectedEditions.insert(edition.code)
|
||||
} else {
|
||||
selectedEditions.remove(edition.code)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}.disabled(worker.isBusy)
|
||||
}
|
||||
Spacer()
|
||||
HStack {
|
||||
ProgressView(value: worker.progress) {
|
||||
|
||||
} currentValueLabel: {
|
||||
Text(worker.progressStatus ?? "")
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Toggle("I agree that I have a valid license to use this product.", isOn: $isTermsAgreed)
|
||||
.disabled(worker.isBusy)
|
||||
Spacer()
|
||||
if worker.isBusy {
|
||||
Button(role: .cancel) {
|
||||
isConfirmCancelShown.toggle()
|
||||
} label: {
|
||||
Text("Cancel")
|
||||
}.keyboardShortcut(.cancelAction)
|
||||
.confirmationDialog("Are you sure you want to stop the process?", isPresented: $isConfirmCancelShown) {
|
||||
Button("Stop", role: .destructive) {
|
||||
worker.stop()
|
||||
}
|
||||
Button("Cancel", role: .cancel) {
|
||||
isConfirmCancelShown = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Button {
|
||||
worker.download(uuid: build.uuid, language: selectedLocale, editions: Array(selectedEditions))
|
||||
} label: {
|
||||
Text("Download…")
|
||||
}.disabled(!isTermsAgreed)
|
||||
}
|
||||
}
|
||||
|
||||
}.padding()
|
||||
.navigationTitle(build.title)
|
||||
.navigationSubtitle(build.arch.prettyArch)
|
||||
.onAppear {
|
||||
worker.refreshDetails(uuid: build.uuid) {
|
||||
details = $0
|
||||
edition = $1
|
||||
selectedEditions.removeAll()
|
||||
selectedEditions.formUnion(edition.editions.map({ $0.code }))
|
||||
if let lastSelectedLocale = lastSelectedLocale {
|
||||
selectedLocale = lastSelectedLocale
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
55
Source/BuildDetails.swift
Normal file
55
Source/BuildDetails.swift
Normal file
|
@ -0,0 +1,55 @@
|
|||
//
|
||||
// Copyright © 2023 Turing Software, LLC. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct BuildDetails {
|
||||
struct Language: Identifiable {
|
||||
let code: String
|
||||
let display: String
|
||||
var id: String {
|
||||
code
|
||||
}
|
||||
}
|
||||
|
||||
private static let unknownLanguage = NSLocalizedString("Unknown Language", comment: "BuildDetails")
|
||||
let languages: [Language]
|
||||
let title: String
|
||||
let ring: String
|
||||
let arch: String
|
||||
let build: String
|
||||
let created: Date
|
||||
|
||||
static var empty = BuildDetails()
|
||||
|
||||
private init() {
|
||||
languages = []
|
||||
title = ""
|
||||
ring = ""
|
||||
arch = ""
|
||||
build = ""
|
||||
created = .distantPast
|
||||
}
|
||||
|
||||
init(from uupDetails: UUPDetails) {
|
||||
languages = uupDetails.langList.map({ Language(code: $0, display: uupDetails.langFancyNames[$0] ?? Self.unknownLanguage) })
|
||||
title = uupDetails.updateInfo.title
|
||||
ring = uupDetails.updateInfo.ring
|
||||
arch = uupDetails.updateInfo.arch
|
||||
build = uupDetails.updateInfo.build
|
||||
created = Date(timeIntervalSince1970: TimeInterval(uupDetails.updateInfo.created))
|
||||
}
|
||||
}
|
40
Source/BuildEditions.swift
Normal file
40
Source/BuildEditions.swift
Normal file
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// Copyright © 2023 Turing Software, LLC. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct BuildEditions {
|
||||
struct Edition: Identifiable {
|
||||
let code: String
|
||||
let display: String
|
||||
var id: String {
|
||||
code
|
||||
}
|
||||
}
|
||||
|
||||
private static let unknownEdition = NSLocalizedString("Unknown Edition", comment: "BuildEditions")
|
||||
let editions: [Edition]
|
||||
|
||||
static var empty = BuildEditions()
|
||||
|
||||
private init() {
|
||||
editions = []
|
||||
}
|
||||
|
||||
init(from uupEditions: UUPEditions) {
|
||||
editions = uupEditions.editionList.map({ Edition(code: $0, display: uupEditions.editionFancyNames[$0] ?? Self.unknownEdition) })
|
||||
}
|
||||
}
|
44
Source/BuildsListView.swift
Normal file
44
Source/BuildsListView.swift
Normal file
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// Copyright © 2023 Turing Software, LLC. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct BuildsListView: View {
|
||||
let arch: String
|
||||
let hasPreviewBuilds: Bool
|
||||
let hasServerBuilds: Bool
|
||||
|
||||
@EnvironmentObject private var worker: Worker
|
||||
|
||||
var body: some View {
|
||||
Section(arch.prettyArch) {
|
||||
ForEach(worker.builds.filter({ $0.arch == arch && (hasPreviewBuilds || !$0.title.contains("Insider")) && (hasServerBuilds || !$0.title.contains("Server")) })) { build in
|
||||
NavigationLink(destination: BuildConfigView(build: build),
|
||||
tag: build,
|
||||
selection: $worker.selectedBuild) {
|
||||
Text(build.title)
|
||||
.lineLimit(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BuildsListView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
BuildsListView(arch: "arm64", hasPreviewBuilds: true, hasServerBuilds: true)
|
||||
}
|
||||
}
|
77
Source/ContentView.swift
Normal file
77
Source/ContentView.swift
Normal file
|
@ -0,0 +1,77 @@
|
|||
//
|
||||
// Copyright © 2023 Turing Software, LLC. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@EnvironmentObject private var worker: Worker
|
||||
@AppStorage("ShowPreviewBuilds") private var hasPreviewBuilds: Bool = false
|
||||
@AppStorage("ShowServerBuilds") private var hasServerBuilds: Bool = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
Group {
|
||||
Toggle("Prerelease Builds", isOn: $hasPreviewBuilds)
|
||||
.help("Show unstable releases which previews upcoming features.")
|
||||
Toggle("Server Builds", isOn: $hasServerBuilds)
|
||||
.help("Show builds for running in a server environment.")
|
||||
#if arch(arm64)
|
||||
BuildsListView(arch: "arm64", hasPreviewBuilds: hasPreviewBuilds, hasServerBuilds: hasServerBuilds)
|
||||
BuildsListView(arch: "amd64", hasPreviewBuilds: hasPreviewBuilds, hasServerBuilds: hasServerBuilds)
|
||||
BuildsListView(arch: "x86", hasPreviewBuilds: hasPreviewBuilds, hasServerBuilds: hasServerBuilds)
|
||||
#else
|
||||
BuildsListView(arch: "amd64", hasPreviewBuilds: hasPreviewBuilds, hasServerBuilds: hasServerBuilds)
|
||||
BuildsListView(arch: "x86", hasPreviewBuilds: hasPreviewBuilds, hasServerBuilds: hasServerBuilds)
|
||||
BuildsListView(arch: "arm64", hasPreviewBuilds: hasPreviewBuilds, hasServerBuilds: hasServerBuilds)
|
||||
#endif
|
||||
}.disabled(worker.isBusy)
|
||||
}.listStyle(.sidebar)
|
||||
.searchable(text: $worker.search, placement: .toolbar)
|
||||
.onSubmit(of: .search) {
|
||||
if !worker.isBusy {
|
||||
worker.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigation) {
|
||||
if worker.isBusy {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .principal) {
|
||||
Button {
|
||||
worker.refresh(findDefault: true)
|
||||
} label: {
|
||||
Label("Refresh", systemImage: "arrow.clockwise")
|
||||
}.disabled(worker.isBusy)
|
||||
}
|
||||
}
|
||||
.alert(item: $worker.lastSeenError) { lastSeenError in
|
||||
Alert(title: Text(lastSeenError.message))
|
||||
}
|
||||
.onAppear {
|
||||
worker.refresh(findDefault: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
}
|
||||
}
|
165
Source/Downloader.swift
Normal file
165
Source/Downloader.swift
Normal file
|
@ -0,0 +1,165 @@
|
|||
//
|
||||
// Copyright © 2023 Turing Software, LLC. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
typealias DownloadItem = (task: URLSessionDownloadTask, destinationUrl: URL, retry: Int)
|
||||
typealias ProgressCallback = (_ bytesWritten: Int64, _ bytesTotal: Int64) -> Void
|
||||
|
||||
actor Downloader {
|
||||
private class Delegate: NSObject, URLSessionDelegate, URLSessionDownloadDelegate {
|
||||
let downloader: Downloader
|
||||
|
||||
init(for downloader: Downloader) {
|
||||
self.downloader = downloader
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
|
||||
let tempUrl = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||
do {
|
||||
try FileManager.default.moveItem(at: location, to: tempUrl)
|
||||
Task {
|
||||
await downloader.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: tempUrl)
|
||||
}
|
||||
} catch {
|
||||
Task {
|
||||
await downloader.urlSession(session, task: downloadTask, didCompleteWithError: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
|
||||
Task {
|
||||
await downloader.urlSession(session, downloadTask: downloadTask, didWriteData: bytesWritten, totalBytesWritten: totalBytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite)
|
||||
}
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
||||
Task {
|
||||
await downloader.urlSession(session, task: task, didCompleteWithError: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let kMaxConcurrentDownloads = 5
|
||||
private let kMaxRetries = 5
|
||||
|
||||
private var downloads: [URLSessionDownloadTask: CheckedContinuation<URL, Error>] = [:]
|
||||
private var queue: [DownloadItem] = []
|
||||
private var totalExpectedSize: Int64 = 0
|
||||
private var totalDownloadedSize: Int64 = 0
|
||||
private var progressCallback: ProgressCallback?
|
||||
|
||||
private lazy var coordinator: Delegate = {
|
||||
Delegate(for: self)
|
||||
}()
|
||||
|
||||
private lazy var session: URLSession = {
|
||||
URLSession(configuration: .default, delegate: coordinator, delegateQueue: nil)
|
||||
}()
|
||||
|
||||
private func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
|
||||
if let continuation = downloads.removeValue(forKey: downloadTask) {
|
||||
continuation.resume(returning: location)
|
||||
} else {
|
||||
try? FileManager.default.removeItem(at: location)
|
||||
}
|
||||
}
|
||||
|
||||
private func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
|
||||
totalDownloadedSize += bytesWritten
|
||||
progressCallback?(totalDownloadedSize, totalExpectedSize)
|
||||
}
|
||||
|
||||
private func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
||||
if let continuation = downloads.removeValue(forKey: task as! URLSessionDownloadTask) {
|
||||
continuation.resume(throwing: error!)
|
||||
}
|
||||
}
|
||||
|
||||
private func register(_ task: URLSessionDownloadTask, continuation: CheckedContinuation<URL, Error>) {
|
||||
downloads[task] = continuation
|
||||
}
|
||||
|
||||
/// Add an item to the download queue
|
||||
/// - Parameters:
|
||||
/// - downloadUrl: What to download
|
||||
/// - destinationUrl: Where to put it
|
||||
/// - size: Estimated size for progress updates
|
||||
func enqueue(downloadUrl: URL, to destinationUrl: URL, size: Int64) {
|
||||
let task = session.downloadTask(with: downloadUrl)
|
||||
queue.append((task: task, destinationUrl: destinationUrl, retry: kMaxRetries))
|
||||
totalExpectedSize += size
|
||||
}
|
||||
|
||||
/// Start downloading a single item from the queue and retry if the download is interrupted
|
||||
private func dequeue() async throws {
|
||||
let (task, destinationUrl, retry) = queue.removeFirst()
|
||||
do {
|
||||
let resultUrl = try await withTaskCancellationHandler {
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
register(task, continuation: continuation)
|
||||
task.resume()
|
||||
}
|
||||
} onCancel: {
|
||||
task.cancel()
|
||||
}
|
||||
try FileManager.default.moveItem(at: resultUrl, to: destinationUrl)
|
||||
} catch {
|
||||
let error = error as NSError
|
||||
if retry > 0 {
|
||||
let newTask: URLSessionDownloadTask
|
||||
if let resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData] as? Data {
|
||||
newTask = session.downloadTask(withResumeData: resumeData)
|
||||
} else {
|
||||
newTask = session.downloadTask(with: task.originalRequest!)
|
||||
totalDownloadedSize -= task.countOfBytesReceived
|
||||
}
|
||||
queue.insert((task: newTask, destinationUrl: destinationUrl, retry: retry - 1), at: 0)
|
||||
return
|
||||
}
|
||||
if error.code == NSURLErrorCancelled {
|
||||
throw CancellationError()
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts downloading all enqueued items
|
||||
/// - Parameter onProgressUpdated: Optional callback for download progress
|
||||
func start(_ onProgressUpdated: ProgressCallback? = nil) async throws {
|
||||
progressCallback = onProgressUpdated
|
||||
defer {
|
||||
progressCallback = nil
|
||||
}
|
||||
try await withThrowingTaskGroup(of: Void.self) { group in
|
||||
for _ in 0..<kMaxConcurrentDownloads {
|
||||
group.addTask {
|
||||
try await self.dequeue()
|
||||
}
|
||||
}
|
||||
for try await _ in group {
|
||||
if !queue.isEmpty {
|
||||
try Task.checkCancellation()
|
||||
group.addTask {
|
||||
try await self.dequeue()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
Source/Glass-ISO-Builder-Info.plist
Normal file
19
Source/Glass-ISO-Builder-Info.plist
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<false/>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>tlu.dl.delivery.mp.microsoft.com</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
12
Source/Glass_ISO_Builder.entitlements
Normal file
12
Source/Glass_ISO_Builder.entitlements
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
28
Source/Main.swift
Normal file
28
Source/Main.swift
Normal file
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// Copyright © 2023 Turing Software, LLC. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct Main: App {
|
||||
@StateObject private var worker = Worker()
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView().environmentObject(worker)
|
||||
}
|
||||
}
|
||||
}
|
39
Source/UUPDump/PrettyString.swift
Normal file
39
Source/UUPDump/PrettyString.swift
Normal file
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// Copyright © 2023 Turing Software, LLC. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
var prettyArch: String {
|
||||
switch self {
|
||||
case "x86": return NSLocalizedString("Intel x86", comment: "PrettyString")
|
||||
case "amd64": return NSLocalizedString("Intel x64", comment: "PrettyString")
|
||||
case "arm64": return NSLocalizedString("Apple Silicon", comment: "PrettyString")
|
||||
default: return NSLocalizedString("Unknown", comment: "PrettyString")
|
||||
}
|
||||
}
|
||||
|
||||
var prettyRing: String {
|
||||
switch self {
|
||||
case "WIF": return NSLocalizedString("Development", comment: "PrettyString")
|
||||
case "WIS": return NSLocalizedString("Beta", comment: "PrettyString")
|
||||
case "CANARY": return NSLocalizedString("Canary", comment: "PrettyString")
|
||||
case "RP": return NSLocalizedString("Release Preview", comment: "PrettyString")
|
||||
case "RETAIL": return NSLocalizedString("Retail", comment: "PrettyString")
|
||||
default: return NSLocalizedString("Unknown", comment: "PrettyString")
|
||||
}
|
||||
}
|
||||
}
|
68
Source/UUPDump/UUPBuilds.swift
Normal file
68
Source/UUPDump/UUPBuilds.swift
Normal file
|
@ -0,0 +1,68 @@
|
|||
//
|
||||
// Copyright © 2023 Turing Software, LLC. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct UUPBuilds: Codable {
|
||||
let apiVersion: String
|
||||
let builds: [Build]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case apiVersion = "apiVersion"
|
||||
case builds = "builds"
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
apiVersion = try values.decode(String.self, forKey: .apiVersion)
|
||||
if let buildsDictionary = try? values.decode([String: Build].self, forKey: .builds) {
|
||||
builds = buildsDictionary.map { $0.1 }
|
||||
} else {
|
||||
builds = try values.decode([Build].self, forKey: .builds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UUPBuilds {
|
||||
struct Build: Codable, Hashable, Identifiable {
|
||||
let title: String
|
||||
let build: String
|
||||
let arch: String
|
||||
let created: Int?
|
||||
let uuid: String
|
||||
|
||||
var id: String {
|
||||
uuid
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case title = "title"
|
||||
case build = "build"
|
||||
case arch = "arch"
|
||||
case created = "created"
|
||||
case uuid = "uuid"
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
title = try values.decode(String.self, forKey: .title)
|
||||
build = try values.decode(String.self, forKey: .build)
|
||||
arch = try values.decode(String.self, forKey: .arch)
|
||||
created = try values.decodeIfPresent(Int.self, forKey: .created)
|
||||
uuid = try values.decode(String.self, forKey: .uuid)
|
||||
}
|
||||
}
|
||||
}
|
78
Source/UUPDump/UUPDetails.swift
Normal file
78
Source/UUPDump/UUPDetails.swift
Normal file
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// Copyright © 2023 Turing Software, LLC. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct UUPDetails: Codable {
|
||||
let apiVersion: String
|
||||
let langList: [String]
|
||||
let langFancyNames: [String: String]
|
||||
let updateInfo: UpdateInfo
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case apiVersion = "apiVersion"
|
||||
case langList = "langList"
|
||||
case langFancyNames = "langFancyNames"
|
||||
case updateInfo = "updateInfo"
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
apiVersion = try values.decode(String.self, forKey: .apiVersion)
|
||||
langList = try values.decode([String].self, forKey: .langList)
|
||||
langFancyNames = try values.decode([String: String].self, forKey: .langFancyNames)
|
||||
updateInfo = try values.decode(UpdateInfo.self, forKey: .updateInfo)
|
||||
}
|
||||
}
|
||||
|
||||
extension UUPDetails {
|
||||
struct UpdateInfo : Codable {
|
||||
let title: String
|
||||
let ring: String
|
||||
let flight: String
|
||||
let arch: String
|
||||
let build: String
|
||||
let checkBuild: String
|
||||
let sku: Int
|
||||
let created: Int
|
||||
let sha256ready: Bool
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case title = "title"
|
||||
case ring = "ring"
|
||||
case flight = "flight"
|
||||
case arch = "arch"
|
||||
case build = "build"
|
||||
case checkBuild = "checkBuild"
|
||||
case sku = "sku"
|
||||
case created = "created"
|
||||
case sha256ready = "sha256ready"
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
title = try values.decode(String.self, forKey: .title)
|
||||
ring = try values.decode(String.self, forKey: .ring)
|
||||
flight = try values.decode(String.self, forKey: .flight)
|
||||
arch = try values.decode(String.self, forKey: .arch)
|
||||
build = try values.decode(String.self, forKey: .build)
|
||||
checkBuild = try values.decode(String.self, forKey: .checkBuild)
|
||||
sku = try values.decode(Int.self, forKey: .sku)
|
||||
created = try values.decode(Int.self, forKey: .created)
|
||||
sha256ready = try values.decode(Bool.self, forKey: .sha256ready)
|
||||
}
|
||||
}
|
||||
}
|
91
Source/UUPDump/UUPDumpAPI.swift
Normal file
91
Source/UUPDump/UUPDumpAPI.swift
Normal file
|
@ -0,0 +1,91 @@
|
|||
//
|
||||
// Copyright © 2023 Turing Software, LLC. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// Do not make more than 1 requests a second to avoid triggering rate limit
|
||||
private let TIMEOUT = TimeInterval(1)
|
||||
private let NS_IN_SECOND = TimeInterval(1000000000)
|
||||
|
||||
actor UUPDumpAPI {
|
||||
private let uupDumpEndpointBase = URL(string: "https://uupdump.net/json-api/")!
|
||||
private var session = URLSession.shared
|
||||
private var lastRequestTime: Date?
|
||||
|
||||
private func makeRequest<Response: Decodable>(endpoint: String, arguments: [String: Any] = [:]) async throws -> Response {
|
||||
let nextRequestTime = lastRequestTime?.advanced(by: TIMEOUT).timeIntervalSinceNow
|
||||
if let nextRequestTime = nextRequestTime, nextRequestTime > 0 {
|
||||
try await Task.sleep(nanoseconds: UInt64(nextRequestTime*NS_IN_SECOND))
|
||||
try Task.checkCancellation()
|
||||
}
|
||||
lastRequestTime = Date.now
|
||||
var components = URLComponents()
|
||||
components.scheme = uupDumpEndpointBase.scheme
|
||||
components.host = uupDumpEndpointBase.host
|
||||
components.path = "\(uupDumpEndpointBase.path)/\(endpoint)"
|
||||
components.queryItems = arguments.flatMap { key, value in
|
||||
if let value = value as? String {
|
||||
return [URLQueryItem(name: key, value: value)]
|
||||
} else if let value = value as? [String] {
|
||||
return value.map({ URLQueryItem(name: "\(key)[]", value: $0) })
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
let (data, _) = try await session.data(from: components.url!)
|
||||
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
|
||||
if let response = json["response"] as? [String: Any] {
|
||||
if let error = response["error"] as? String {
|
||||
throw UUPDumpAPIError.errorResponse(error)
|
||||
} else {
|
||||
let wrapped = try JSONSerialization.data(withJSONObject: response)
|
||||
return try JSONDecoder().decode(Response.self, from: wrapped)
|
||||
}
|
||||
}
|
||||
}
|
||||
throw UUPDumpAPIError.responseNotFound
|
||||
}
|
||||
|
||||
func fetchBuilds(search: String? = nil) async throws -> UUPBuilds {
|
||||
return try await makeRequest(endpoint: "listid.php", arguments: ["search": search ?? ""])
|
||||
}
|
||||
|
||||
func fetchDetails(for uuid: String) async throws -> UUPDetails {
|
||||
return try await makeRequest(endpoint: "listlangs.php", arguments: ["id": uuid])
|
||||
}
|
||||
|
||||
func fetchEditions(for uuid: String, language: String = "neutral") async throws -> UUPEditions {
|
||||
return try await makeRequest(endpoint: "listeditions.php", arguments: ["id": uuid, "lang": language])
|
||||
}
|
||||
|
||||
func fetchPackage(for uuid: String, language: String = "neutral", editions: [String] = []) async throws -> UUPPackage {
|
||||
return try await makeRequest(endpoint: "get.php", arguments: ["id": uuid, "lang": language, "edition": editions])
|
||||
}
|
||||
}
|
||||
|
||||
enum UUPDumpAPIError: Error {
|
||||
case responseNotFound
|
||||
case errorResponse(String)
|
||||
}
|
||||
|
||||
extension UUPDumpAPIError: LocalizedError {
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .responseNotFound: return NSLocalizedString("Cannot find data from the server response.", comment: "UUPDumpAPI")
|
||||
case .errorResponse(let message): return String.localizedStringWithFormat(NSLocalizedString("Error returned from server: %@", comment: "UUPDumpAPI"), message)
|
||||
}
|
||||
}
|
||||
}
|
36
Source/UUPDump/UUPEditions.swift
Normal file
36
Source/UUPDump/UUPEditions.swift
Normal file
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// Copyright © 2023 Turing Software, LLC. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct UUPEditions: Codable {
|
||||
let apiVersion: String
|
||||
let editionList: [String]
|
||||
let editionFancyNames: [String: String]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case apiVersion = "apiVersion"
|
||||
case editionList = "editionList"
|
||||
case editionFancyNames = "editionFancyNames"
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
apiVersion = try values.decode(String.self, forKey: .apiVersion)
|
||||
editionList = try values.decode([String].self, forKey: .editionList)
|
||||
editionFancyNames = try values.decode([String: String].self, forKey: .editionFancyNames)
|
||||
}
|
||||
}
|
87
Source/UUPDump/UUPPackage.swift
Normal file
87
Source/UUPDump/UUPPackage.swift
Normal file
|
@ -0,0 +1,87 @@
|
|||
//
|
||||
// Copyright © 2023 Turing Software, LLC. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct UUPPackage: Codable {
|
||||
let apiVersion: String
|
||||
let updateName: String
|
||||
let arch: String
|
||||
let build: String
|
||||
let sku: Int
|
||||
let hasUpdates: Bool
|
||||
let files: [String: File]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case apiVersion = "apiVersion"
|
||||
case updateName = "updateName"
|
||||
case arch = "arch"
|
||||
case build = "build"
|
||||
case sku = "sku"
|
||||
case hasUpdates = "hasUpdates"
|
||||
case files = "files"
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
apiVersion = try values.decode(String.self, forKey: .apiVersion)
|
||||
updateName = try values.decode(String.self, forKey: .updateName)
|
||||
arch = try values.decode(String.self, forKey: .arch)
|
||||
build = try values.decode(String.self, forKey: .build)
|
||||
sku = try values.decode(Int.self, forKey: .sku)
|
||||
hasUpdates = try values.decode(Bool.self, forKey: .hasUpdates)
|
||||
files = try values.decode([String: File].self, forKey: .files)
|
||||
}
|
||||
}
|
||||
|
||||
extension UUPPackage {
|
||||
struct File: Codable {
|
||||
let sha1: String?
|
||||
let sha256: String?
|
||||
let size: Int64
|
||||
let url: String
|
||||
let uuid: String
|
||||
let expire: Int
|
||||
let debug: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case sha1 = "sha1"
|
||||
case sha256 = "sha256"
|
||||
case size = "size"
|
||||
case url = "url"
|
||||
case uuid = "uuid"
|
||||
case expire = "expire"
|
||||
case debug = "debug"
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
sha1 = try values.decodeIfPresent(String.self, forKey: .sha1)
|
||||
sha256 = try values.decodeIfPresent(String.self, forKey: .sha256)
|
||||
if let sizeInt = try? values.decodeIfPresent(Int64.self, forKey: .size) {
|
||||
size = sizeInt
|
||||
} else if let sizeStr = try values.decodeIfPresent(String.self, forKey: .size) {
|
||||
size = Int64(sizeStr) ?? 0
|
||||
} else {
|
||||
size = 0
|
||||
}
|
||||
url = try values.decode(String.self, forKey: .url)
|
||||
uuid = try values.decode(String.self, forKey: .uuid)
|
||||
expire = try values.decode(Int.self, forKey: .expire)
|
||||
debug = try values.decodeIfPresent(String.self, forKey: .debug)
|
||||
}
|
||||
}
|
||||
}
|
139
Source/Worker.swift
Normal file
139
Source/Worker.swift
Normal file
|
@ -0,0 +1,139 @@
|
|||
//
|
||||
// Copyright © 2023 Turing Software, LLC. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
class Worker: ObservableObject {
|
||||
struct ErrorMessage: Identifiable {
|
||||
var message: String
|
||||
|
||||
var id: String {
|
||||
message
|
||||
}
|
||||
}
|
||||
|
||||
@Published private(set) var isBusy: Bool = false
|
||||
@Published var lastSeenError: ErrorMessage?
|
||||
@Published var builds: [UUPBuilds.Build] = []
|
||||
@Published var selectedBuild: UUPBuilds.Build?
|
||||
@Published var search: String = ""
|
||||
@Published private(set) var progress: Float = 0.0
|
||||
@Published private(set) var progressStatus: String?
|
||||
@Published var completedDownloadUrl: URL?
|
||||
|
||||
private let api = UUPDumpAPI()
|
||||
private var runningTask: (Task<Void, Never>)?
|
||||
|
||||
private var currentArch: String {
|
||||
#if arch(arm64)
|
||||
"arm64"
|
||||
#else
|
||||
"amd64"
|
||||
#endif
|
||||
}
|
||||
|
||||
private var hasPreviewBuilds: Bool {
|
||||
UserDefaults.standard.bool(forKey: "ShowPreviewBuilds")
|
||||
}
|
||||
|
||||
private var hasServerBuilds: Bool {
|
||||
UserDefaults.standard.bool(forKey: "ShowServerBuilds")
|
||||
}
|
||||
|
||||
var defaultLocale: String {
|
||||
UserDefaults.standard.string(forKey: "LastSelectedLocale") ?? Locale.preferredLanguages.first?.lowercased() ?? "netural"
|
||||
}
|
||||
|
||||
func refresh(findDefault: Bool = false) {
|
||||
withBusyIndication { [self] in
|
||||
let lastSelectedUuid = selectedBuild?.uuid
|
||||
let response = try await api.fetchBuilds(search: search.count > 0 ? search : nil)
|
||||
builds = response.builds.filter({ !$0.title.lowercased().contains("update") })
|
||||
if findDefault || !builds.contains(where: { $0.uuid == lastSelectedUuid }) {
|
||||
selectedBuild = recommendedBuild()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func refreshDetails(uuid: String, language: String? = nil, _ onCompletion: @escaping (BuildDetails, BuildEditions) -> Void) {
|
||||
withBusyIndication { [self] in
|
||||
let detailsResponse = try await api.fetchDetails(for: uuid)
|
||||
let editionsResponse = try await api.fetchEditions(for: uuid, language: language ?? defaultLocale)
|
||||
onCompletion(BuildDetails(from: detailsResponse), BuildEditions(from: editionsResponse))
|
||||
}
|
||||
}
|
||||
|
||||
private func recommendedBuild() -> UUPBuilds.Build? {
|
||||
builds.filter({ $0.arch == currentArch && (hasPreviewBuilds || !$0.title.contains("Insider")) && (hasServerBuilds || !$0.title.contains("Server")) }).first
|
||||
}
|
||||
|
||||
func download(uuid: String, language: String, editions: [String]) {
|
||||
let cacheUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||
let baseUrl = cacheUrl.appendingPathComponent(uuid)
|
||||
withBusyIndication { [self] in
|
||||
let fm = FileManager.default
|
||||
let files = try? fm.contentsOfDirectory(at: baseUrl, includingPropertiesForKeys: nil)
|
||||
if let existingUrl = files?.first(where: { $0.pathExtension == "iso" }) {
|
||||
// if we already have the ISO, go ahead and return it
|
||||
completedDownloadUrl = existingUrl
|
||||
return
|
||||
}
|
||||
try? fm.removeItem(at: baseUrl)
|
||||
try fm.createDirectory(at: baseUrl, withIntermediateDirectories: true)
|
||||
completedDownloadUrl = nil
|
||||
progress = 0.0
|
||||
progressStatus = NSLocalizedString("Fetching files list...", comment: "Worker")
|
||||
let package = try await api.fetchPackage(for: uuid, language: language, editions: editions)
|
||||
progressStatus = NSLocalizedString("Starting download...", comment: "Worker")
|
||||
let downloader = Downloader()
|
||||
for (key, value) in package.files {
|
||||
await downloader.enqueue(downloadUrl: URL(string: value.url)!, to: baseUrl.appendingPathComponent(key), size: value.size)
|
||||
}
|
||||
try await downloader.start { bytesWritten, bytesTotal in
|
||||
let written = ByteCountFormatter.string(fromByteCount: bytesWritten, countStyle: .file)
|
||||
let total = ByteCountFormatter.string(fromByteCount: bytesTotal, countStyle: .file)
|
||||
Task { @MainActor in
|
||||
self.progressStatus = String.localizedStringWithFormat(NSLocalizedString("Downloading %@ of %@...", comment: "Worker"), written, total)
|
||||
self.progress = (Float(bytesWritten) / Float(bytesTotal)) * 0.9
|
||||
}
|
||||
}
|
||||
progressStatus = NSLocalizedString("Converting download to ISO...", comment: "Worker")
|
||||
throw CancellationError()
|
||||
}
|
||||
}
|
||||
|
||||
func stop() {
|
||||
runningTask?.cancel()
|
||||
}
|
||||
|
||||
private func withBusyIndication(_ action: @escaping @MainActor () async throws -> Void) {
|
||||
isBusy = true
|
||||
runningTask = Task {
|
||||
do {
|
||||
try await action()
|
||||
} catch is CancellationError {
|
||||
|
||||
} catch {
|
||||
lastSeenError = ErrorMessage(message: error.localizedDescription)
|
||||
}
|
||||
runningTask = nil
|
||||
isBusy = false
|
||||
progress = 0.0
|
||||
progressStatus = nil
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue