From db41140fbbdc1ea3b7d5fc88dc06bce57e32f4c6 Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 12 Feb 2024 18:39:41 +0100 Subject: [PATCH 001/125] WIP --- .../starskydesktop.xcodeproj/project.pbxproj | 578 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 58 ++ .../Assets.xcassets/Contents.json | 6 + .../starskydesktop/ContentView.swift | 24 + .../Preview Assets.xcassets/Contents.json | 6 + .../starskydesktop.entitlements | 10 + .../starskydesktop/starskydesktopApp.swift | 17 + .../starskydesktopTests.swift | 36 ++ .../starskydesktopUITests.swift | 41 ++ .../starskydesktopUITestsLaunchTests.swift | 32 + .../Trash/Helpers/MacOsLaunch.cs | 120 ++++ .../Trash/Helpers/MacOsOpen.cs | 142 +++++ .../Trash/Helpers/MacOsTest2.cs | 76 +++ .../Trash/Helpers/Test2Class.cs | 17 + 17 files changed, 1189 insertions(+) create mode 100644 desktopmac/starskydesktop/starskydesktop.xcodeproj/project.pbxproj create mode 100644 desktopmac/starskydesktop/starskydesktop.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 desktopmac/starskydesktop/starskydesktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 desktopmac/starskydesktop/starskydesktop/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 desktopmac/starskydesktop/starskydesktop/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 desktopmac/starskydesktop/starskydesktop/Assets.xcassets/Contents.json create mode 100644 desktopmac/starskydesktop/starskydesktop/ContentView.swift create mode 100644 desktopmac/starskydesktop/starskydesktop/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 desktopmac/starskydesktop/starskydesktop/starskydesktop.entitlements create mode 100644 desktopmac/starskydesktop/starskydesktop/starskydesktopApp.swift create mode 100644 desktopmac/starskydesktop/starskydesktopTests/starskydesktopTests.swift create mode 100644 desktopmac/starskydesktop/starskydesktopUITests/starskydesktopUITests.swift create mode 100644 desktopmac/starskydesktop/starskydesktopUITests/starskydesktopUITestsLaunchTests.swift create mode 100644 starsky/starsky.foundation.native/Trash/Helpers/MacOsLaunch.cs create mode 100644 starsky/starsky.foundation.native/Trash/Helpers/MacOsOpen.cs create mode 100644 starsky/starsky.foundation.native/Trash/Helpers/MacOsTest2.cs create mode 100644 starsky/starskytest/starsky.foundation.native/Trash/Helpers/Test2Class.cs diff --git a/desktopmac/starskydesktop/starskydesktop.xcodeproj/project.pbxproj b/desktopmac/starskydesktop/starskydesktop.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..0135a12576 --- /dev/null +++ b/desktopmac/starskydesktop/starskydesktop.xcodeproj/project.pbxproj @@ -0,0 +1,578 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + BCF3FD0A2B7A780B00EC31E2 /* starskydesktopApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCF3FD092B7A780B00EC31E2 /* starskydesktopApp.swift */; }; + BCF3FD0C2B7A780B00EC31E2 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCF3FD0B2B7A780B00EC31E2 /* ContentView.swift */; }; + BCF3FD0E2B7A780C00EC31E2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BCF3FD0D2B7A780C00EC31E2 /* Assets.xcassets */; }; + BCF3FD112B7A780C00EC31E2 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BCF3FD102B7A780C00EC31E2 /* Preview Assets.xcassets */; }; + BCF3FD1C2B7A780C00EC31E2 /* starskydesktopTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCF3FD1B2B7A780C00EC31E2 /* starskydesktopTests.swift */; }; + BCF3FD262B7A780C00EC31E2 /* starskydesktopUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCF3FD252B7A780C00EC31E2 /* starskydesktopUITests.swift */; }; + BCF3FD282B7A780C00EC31E2 /* starskydesktopUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCF3FD272B7A780C00EC31E2 /* starskydesktopUITestsLaunchTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + BCF3FD182B7A780C00EC31E2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BCF3FCFE2B7A780B00EC31E2 /* Project object */; + proxyType = 1; + remoteGlobalIDString = BCF3FD052B7A780B00EC31E2; + remoteInfo = starskydesktop; + }; + BCF3FD222B7A780C00EC31E2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BCF3FCFE2B7A780B00EC31E2 /* Project object */; + proxyType = 1; + remoteGlobalIDString = BCF3FD052B7A780B00EC31E2; + remoteInfo = starskydesktop; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + BCF3FD062B7A780B00EC31E2 /* starskydesktop.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = starskydesktop.app; sourceTree = BUILT_PRODUCTS_DIR; }; + BCF3FD092B7A780B00EC31E2 /* starskydesktopApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = starskydesktopApp.swift; sourceTree = ""; }; + BCF3FD0B2B7A780B00EC31E2 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + BCF3FD0D2B7A780C00EC31E2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + BCF3FD102B7A780C00EC31E2 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + BCF3FD122B7A780C00EC31E2 /* starskydesktop.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = starskydesktop.entitlements; sourceTree = ""; }; + BCF3FD172B7A780C00EC31E2 /* starskydesktopTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = starskydesktopTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + BCF3FD1B2B7A780C00EC31E2 /* starskydesktopTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = starskydesktopTests.swift; sourceTree = ""; }; + BCF3FD212B7A780C00EC31E2 /* starskydesktopUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = starskydesktopUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + BCF3FD252B7A780C00EC31E2 /* starskydesktopUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = starskydesktopUITests.swift; sourceTree = ""; }; + BCF3FD272B7A780C00EC31E2 /* starskydesktopUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = starskydesktopUITestsLaunchTests.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + BCF3FD032B7A780B00EC31E2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BCF3FD142B7A780C00EC31E2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BCF3FD1E2B7A780C00EC31E2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + BCF3FCFD2B7A780B00EC31E2 = { + isa = PBXGroup; + children = ( + BCF3FD082B7A780B00EC31E2 /* starskydesktop */, + BCF3FD1A2B7A780C00EC31E2 /* starskydesktopTests */, + BCF3FD242B7A780C00EC31E2 /* starskydesktopUITests */, + BCF3FD072B7A780B00EC31E2 /* Products */, + ); + sourceTree = ""; + }; + BCF3FD072B7A780B00EC31E2 /* Products */ = { + isa = PBXGroup; + children = ( + BCF3FD062B7A780B00EC31E2 /* starskydesktop.app */, + BCF3FD172B7A780C00EC31E2 /* starskydesktopTests.xctest */, + BCF3FD212B7A780C00EC31E2 /* starskydesktopUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + BCF3FD082B7A780B00EC31E2 /* starskydesktop */ = { + isa = PBXGroup; + children = ( + BCF3FD092B7A780B00EC31E2 /* starskydesktopApp.swift */, + BCF3FD0B2B7A780B00EC31E2 /* ContentView.swift */, + BCF3FD0D2B7A780C00EC31E2 /* Assets.xcassets */, + BCF3FD122B7A780C00EC31E2 /* starskydesktop.entitlements */, + BCF3FD0F2B7A780C00EC31E2 /* Preview Content */, + ); + path = starskydesktop; + sourceTree = ""; + }; + BCF3FD0F2B7A780C00EC31E2 /* Preview Content */ = { + isa = PBXGroup; + children = ( + BCF3FD102B7A780C00EC31E2 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + BCF3FD1A2B7A780C00EC31E2 /* starskydesktopTests */ = { + isa = PBXGroup; + children = ( + BCF3FD1B2B7A780C00EC31E2 /* starskydesktopTests.swift */, + ); + path = starskydesktopTests; + sourceTree = ""; + }; + BCF3FD242B7A780C00EC31E2 /* starskydesktopUITests */ = { + isa = PBXGroup; + children = ( + BCF3FD252B7A780C00EC31E2 /* starskydesktopUITests.swift */, + BCF3FD272B7A780C00EC31E2 /* starskydesktopUITestsLaunchTests.swift */, + ); + path = starskydesktopUITests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + BCF3FD052B7A780B00EC31E2 /* starskydesktop */ = { + isa = PBXNativeTarget; + buildConfigurationList = BCF3FD2B2B7A780C00EC31E2 /* Build configuration list for PBXNativeTarget "starskydesktop" */; + buildPhases = ( + BCF3FD022B7A780B00EC31E2 /* Sources */, + BCF3FD032B7A780B00EC31E2 /* Frameworks */, + BCF3FD042B7A780B00EC31E2 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = starskydesktop; + productName = starskydesktop; + productReference = BCF3FD062B7A780B00EC31E2 /* starskydesktop.app */; + productType = "com.apple.product-type.application"; + }; + BCF3FD162B7A780C00EC31E2 /* starskydesktopTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = BCF3FD2E2B7A780C00EC31E2 /* Build configuration list for PBXNativeTarget "starskydesktopTests" */; + buildPhases = ( + BCF3FD132B7A780C00EC31E2 /* Sources */, + BCF3FD142B7A780C00EC31E2 /* Frameworks */, + BCF3FD152B7A780C00EC31E2 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + BCF3FD192B7A780C00EC31E2 /* PBXTargetDependency */, + ); + name = starskydesktopTests; + productName = starskydesktopTests; + productReference = BCF3FD172B7A780C00EC31E2 /* starskydesktopTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + BCF3FD202B7A780C00EC31E2 /* starskydesktopUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = BCF3FD312B7A780C00EC31E2 /* Build configuration list for PBXNativeTarget "starskydesktopUITests" */; + buildPhases = ( + BCF3FD1D2B7A780C00EC31E2 /* Sources */, + BCF3FD1E2B7A780C00EC31E2 /* Frameworks */, + BCF3FD1F2B7A780C00EC31E2 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + BCF3FD232B7A780C00EC31E2 /* PBXTargetDependency */, + ); + name = starskydesktopUITests; + productName = starskydesktopUITests; + productReference = BCF3FD212B7A780C00EC31E2 /* starskydesktopUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + BCF3FCFE2B7A780B00EC31E2 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1520; + LastUpgradeCheck = 1520; + TargetAttributes = { + BCF3FD052B7A780B00EC31E2 = { + CreatedOnToolsVersion = 15.2; + }; + BCF3FD162B7A780C00EC31E2 = { + CreatedOnToolsVersion = 15.2; + TestTargetID = BCF3FD052B7A780B00EC31E2; + }; + BCF3FD202B7A780C00EC31E2 = { + CreatedOnToolsVersion = 15.2; + TestTargetID = BCF3FD052B7A780B00EC31E2; + }; + }; + }; + buildConfigurationList = BCF3FD012B7A780B00EC31E2 /* Build configuration list for PBXProject "starskydesktop" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = BCF3FCFD2B7A780B00EC31E2; + productRefGroup = BCF3FD072B7A780B00EC31E2 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + BCF3FD052B7A780B00EC31E2 /* starskydesktop */, + BCF3FD162B7A780C00EC31E2 /* starskydesktopTests */, + BCF3FD202B7A780C00EC31E2 /* starskydesktopUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + BCF3FD042B7A780B00EC31E2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BCF3FD112B7A780C00EC31E2 /* Preview Assets.xcassets in Resources */, + BCF3FD0E2B7A780C00EC31E2 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BCF3FD152B7A780C00EC31E2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BCF3FD1F2B7A780C00EC31E2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + BCF3FD022B7A780B00EC31E2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BCF3FD0C2B7A780B00EC31E2 /* ContentView.swift in Sources */, + BCF3FD0A2B7A780B00EC31E2 /* starskydesktopApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BCF3FD132B7A780C00EC31E2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BCF3FD1C2B7A780C00EC31E2 /* starskydesktopTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BCF3FD1D2B7A780C00EC31E2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BCF3FD262B7A780C00EC31E2 /* starskydesktopUITests.swift in Sources */, + BCF3FD282B7A780C00EC31E2 /* starskydesktopUITestsLaunchTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + BCF3FD192B7A780C00EC31E2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = BCF3FD052B7A780B00EC31E2 /* starskydesktop */; + targetProxy = BCF3FD182B7A780C00EC31E2 /* PBXContainerItemProxy */; + }; + BCF3FD232B7A780C00EC31E2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = BCF3FD052B7A780B00EC31E2 /* starskydesktop */; + targetProxy = BCF3FD222B7A780C00EC31E2 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + BCF3FD292B7A780C00EC31E2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + 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; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + 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; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + BCF3FD2A2B7A780C00EC31E2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + 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; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + 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; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + BCF3FD2C2B7A780C00EC31E2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = starskydesktop/starskydesktop.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"starskydesktop/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = nl.qdraw.starskydesktop; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + BCF3FD2D2B7A780C00EC31E2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = starskydesktop/starskydesktop.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"starskydesktop/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = nl.qdraw.starskydesktop; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + BCF3FD2F2B7A780C00EC31E2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 14.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = nl.qdraw.starskydesktopTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/starskydesktop.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/starskydesktop"; + }; + name = Debug; + }; + BCF3FD302B7A780C00EC31E2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 14.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = nl.qdraw.starskydesktopTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/starskydesktop.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/starskydesktop"; + }; + name = Release; + }; + BCF3FD322B7A780C00EC31E2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = nl.qdraw.starskydesktopUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = starskydesktop; + }; + name = Debug; + }; + BCF3FD332B7A780C00EC31E2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = nl.qdraw.starskydesktopUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = starskydesktop; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + BCF3FD012B7A780B00EC31E2 /* Build configuration list for PBXProject "starskydesktop" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BCF3FD292B7A780C00EC31E2 /* Debug */, + BCF3FD2A2B7A780C00EC31E2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + BCF3FD2B2B7A780C00EC31E2 /* Build configuration list for PBXNativeTarget "starskydesktop" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BCF3FD2C2B7A780C00EC31E2 /* Debug */, + BCF3FD2D2B7A780C00EC31E2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + BCF3FD2E2B7A780C00EC31E2 /* Build configuration list for PBXNativeTarget "starskydesktopTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BCF3FD2F2B7A780C00EC31E2 /* Debug */, + BCF3FD302B7A780C00EC31E2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + BCF3FD312B7A780C00EC31E2 /* Build configuration list for PBXNativeTarget "starskydesktopUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BCF3FD322B7A780C00EC31E2 /* Debug */, + BCF3FD332B7A780C00EC31E2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = BCF3FCFE2B7A780B00EC31E2 /* Project object */; +} diff --git a/desktopmac/starskydesktop/starskydesktop.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/desktopmac/starskydesktop/starskydesktop.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..919434a625 --- /dev/null +++ b/desktopmac/starskydesktop/starskydesktop.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/desktopmac/starskydesktop/starskydesktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/desktopmac/starskydesktop/starskydesktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/desktopmac/starskydesktop/starskydesktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/desktopmac/starskydesktop/starskydesktop/Assets.xcassets/AccentColor.colorset/Contents.json b/desktopmac/starskydesktop/starskydesktop/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000000..eb87897008 --- /dev/null +++ b/desktopmac/starskydesktop/starskydesktop/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/desktopmac/starskydesktop/starskydesktop/Assets.xcassets/AppIcon.appiconset/Contents.json b/desktopmac/starskydesktop/starskydesktop/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..3f00db43ec --- /dev/null +++ b/desktopmac/starskydesktop/starskydesktop/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -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 + } +} diff --git a/desktopmac/starskydesktop/starskydesktop/Assets.xcassets/Contents.json b/desktopmac/starskydesktop/starskydesktop/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/desktopmac/starskydesktop/starskydesktop/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/desktopmac/starskydesktop/starskydesktop/ContentView.swift b/desktopmac/starskydesktop/starskydesktop/ContentView.swift new file mode 100644 index 0000000000..30a3066e95 --- /dev/null +++ b/desktopmac/starskydesktop/starskydesktop/ContentView.swift @@ -0,0 +1,24 @@ +// +// ContentView.swift +// starskydesktop +// +// Created by Dion on 12/02/2024. +// + +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + } + .padding() + } +} + +#Preview { + ContentView() +} diff --git a/desktopmac/starskydesktop/starskydesktop/Preview Content/Preview Assets.xcassets/Contents.json b/desktopmac/starskydesktop/starskydesktop/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/desktopmac/starskydesktop/starskydesktop/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/desktopmac/starskydesktop/starskydesktop/starskydesktop.entitlements b/desktopmac/starskydesktop/starskydesktop/starskydesktop.entitlements new file mode 100644 index 0000000000..18aff0ce43 --- /dev/null +++ b/desktopmac/starskydesktop/starskydesktop/starskydesktop.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/desktopmac/starskydesktop/starskydesktop/starskydesktopApp.swift b/desktopmac/starskydesktop/starskydesktop/starskydesktopApp.swift new file mode 100644 index 0000000000..aa19d3eafd --- /dev/null +++ b/desktopmac/starskydesktop/starskydesktop/starskydesktopApp.swift @@ -0,0 +1,17 @@ +// +// starskydesktopApp.swift +// starskydesktop +// +// Created by Dion on 12/02/2024. +// + +import SwiftUI + +@main +struct starskydesktopApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/desktopmac/starskydesktop/starskydesktopTests/starskydesktopTests.swift b/desktopmac/starskydesktop/starskydesktopTests/starskydesktopTests.swift new file mode 100644 index 0000000000..f159dcb600 --- /dev/null +++ b/desktopmac/starskydesktop/starskydesktopTests/starskydesktopTests.swift @@ -0,0 +1,36 @@ +// +// starskydesktopTests.swift +// starskydesktopTests +// +// Created by Dion on 12/02/2024. +// + +import XCTest +@testable import starskydesktop + +final class starskydesktopTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/desktopmac/starskydesktop/starskydesktopUITests/starskydesktopUITests.swift b/desktopmac/starskydesktop/starskydesktopUITests/starskydesktopUITests.swift new file mode 100644 index 0000000000..56e1fc45dc --- /dev/null +++ b/desktopmac/starskydesktop/starskydesktopUITests/starskydesktopUITests.swift @@ -0,0 +1,41 @@ +// +// starskydesktopUITests.swift +// starskydesktopUITests +// +// Created by Dion on 12/02/2024. +// + +import XCTest + +final class starskydesktopUITests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testLaunchPerformance() throws { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } + } +} diff --git a/desktopmac/starskydesktop/starskydesktopUITests/starskydesktopUITestsLaunchTests.swift b/desktopmac/starskydesktop/starskydesktopUITests/starskydesktopUITestsLaunchTests.swift new file mode 100644 index 0000000000..0e7bbad94a --- /dev/null +++ b/desktopmac/starskydesktop/starskydesktopUITests/starskydesktopUITestsLaunchTests.swift @@ -0,0 +1,32 @@ +// +// starskydesktopUITestsLaunchTests.swift +// starskydesktopUITests +// +// Created by Dion on 12/02/2024. +// + +import XCTest + +final class starskydesktopUITestsLaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +} diff --git a/starsky/starsky.foundation.native/Trash/Helpers/MacOsLaunch.cs b/starsky/starsky.foundation.native/Trash/Helpers/MacOsLaunch.cs new file mode 100644 index 0000000000..f554710886 --- /dev/null +++ b/starsky/starsky.foundation.native/Trash/Helpers/MacOsLaunch.cs @@ -0,0 +1,120 @@ +// using System.Runtime.InteropServices; +// +// namespace starsky.foundation.native.Trash.Helpers; +// +// public class MacOsLaunch +// { +// private const string FoundationFramework = "/System/Library/Frameworks/Foundation.framework/Foundation"; +// private const string AppKitFramework = "/System/Library/Frameworks/AppKit.framework/AppKit"; +// +// [DllImport(AppKitFramework)] +// private static extern IntPtr NSSelectorFromString(IntPtr cfstr); +// +// [DllImport(FoundationFramework)] +// private static extern void CFRelease(IntPtr handle); +// +// public class ApplicationStartInfo +// { +// public ApplicationStartInfo (string application) +// { +// this.Application = application; +// this.Environment = new Dictionary (); +// } +// +// public string Application { get; set; } +// public Dictionary Environment { get; private set; } +// public string[] Args { get; set; } +// public bool Async { get; set; } +// public bool NewInstance { get; set; } +// } +// +// +// public static class LaunchServices +// { +// public static int OpenApplication (string application) +// { +// return OpenApplication (new ApplicationStartInfo (application)); +// } +// +// internal static IntPtr GetSelector(string name) +// { +// var cfStrSelector = MacOsTrashBindingHelper.CreateCfString(name); +// var selector = NSSelectorFromString(cfStrSelector); +// CFRelease(cfStrSelector); +// return selector; +// } +// +// // This function can be replaced by NSWorkspace.LaunchApplication but it currently doesn't work +// // https://bugzilla.xamarin.com/show_bug.cgi?id=32540 +// +// [DllImport ("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")] +// static extern IntPtr IntPtr_objc_msgSend_IntPtr_UInt32_IntPtr_IntPtr (IntPtr receiver, IntPtr selector, IntPtr url, UInt32 options, IntPtr configuration, out IntPtr error); +// static readonly IntPtr launchApplicationAtURLOptionsConfigurationErrorSelector = ObjCRuntime.Selector.GetHandle ("launchApplicationAtURL:options:configuration:error:"); +// public static int OpenApplication (ApplicationStartInfo application) +// { +// if (application == null) +// throw new ArgumentNullException ("application"); +// +// if (string.IsNullOrEmpty (application.Application) || !Directory.Exists (application.Application)) +// throw new ArgumentException ("Application is not valid"); +// +// NSUrl appUrl = NSUrl.FromFilename (application.Application); +// +// // TODO: Once the above bug is fixed, we can replace the code below with +// //NSRunningApplication app = NSWorkspace.SharedWorkspace.LaunchApplication (appUrl, 0, new NSDictionary (), null); +// +// var config = new NSMutableDictionary (); +// if (application.Args != null && application.Args.Length > 0) { +// var args = new NSMutableArray (); +// foreach (string arg in application.Args) { +// args.Add (new NSString (arg)); +// } +// config.Add (new NSString ("NSWorkspaceLaunchConfigurationArguments"), args); +// } +// +// if (application.Environment != null && application.Environment.Count > 0) { +// var envValueStrings = application.Environment.Values.Select (t => new NSString (t)).ToArray (); +// var envKeyStrings = application.Environment.Keys.Select (t => new NSString (t)).ToArray (); +// +// var envDict = new NSMutableDictionary (); +// for (int i = 0; i < envValueStrings.Length; i++) { +// envDict.Add (envKeyStrings[i], envValueStrings[i]); +// } +// +// config.Add (new NSString ("NSWorkspaceLaunchConfigurationEnvironment"), envDict); +// } +// +// UInt32 options = 0; +// +// if (application.Async) +// options |= (UInt32) LaunchOptions.NSWorkspaceLaunchAsync; +// if (application.NewInstance) +// options |= (UInt32) LaunchOptions.NSWorkspaceLaunchNewInstance; +// +// IntPtr error; +// var appHandle = IntPtr_objc_msgSend_IntPtr_UInt32_IntPtr_IntPtr (NSWorkspace.SharedWorkspace.Handle, launchApplicationAtURLOptionsConfigurationErrorSelector, appUrl.Handle, options, config.Handle, out error); +// if (appHandle == IntPtr.Zero) +// return -1; +// +// NSRunningApplication app = (NSRunningApplication)ObjCRuntime.Runtime.GetNSObject (appHandle); +// +// return app.ProcessIdentifier; +// } +// +// [Flags] +// enum LaunchOptions { +// NSWorkspaceLaunchAndPrint = 0x00000002, +// NSWorkspaceLaunchWithErrorPresentation = 0x00000040, +// NSWorkspaceLaunchInhibitingBackgroundOnly = 0x00000080, +// NSWorkspaceLaunchWithoutAddingToRecents = 0x00000100, +// NSWorkspaceLaunchWithoutActivation = 0x00000200, +// NSWorkspaceLaunchAsync = 0x00010000, +// NSWorkspaceLaunchAllowingClassicStartup = 0x00020000, +// NSWorkspaceLaunchPreferringClassic = 0x00040000, +// NSWorkspaceLaunchNewInstance = 0x00080000, +// NSWorkspaceLaunchAndHide = 0x00100000, +// NSWorkspaceLaunchAndHideOthers = 0x00200000, +// NSWorkspaceLaunchDefault = NSWorkspaceLaunchAsync | NSWorkspaceLaunchAllowingClassicStartup +// }; +// } +// } diff --git a/starsky/starsky.foundation.native/Trash/Helpers/MacOsOpen.cs b/starsky/starsky.foundation.native/Trash/Helpers/MacOsOpen.cs new file mode 100644 index 0000000000..9e9bceb6ad --- /dev/null +++ b/starsky/starsky.foundation.native/Trash/Helpers/MacOsOpen.cs @@ -0,0 +1,142 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Text; + +namespace starsky.foundation.native.Trash.Helpers; + +public class MacOsOpen +{ + private const string AppKitFramework = "/System/Library/Frameworks/AppKit.framework/AppKit"; + + private const string FoundationFramework = + "/System/Library/Frameworks/Foundation.framework/Foundation"; + + + private enum CFStringEncoding : uint + { + UTF16 = 0x0100, + UTF16BE = 0x10000100, + UTF16LE = 0x14000100, + ASCII = 0x0600 + } + + /// + /// Native methods we can call on the Mac. + /// + private static class NativeMethods + { + private const string FoundationFramework = + "/System/Library/Frameworks/Foundation.framework/Foundation"; + + private const string AppKitFramework = "/System/Library/Frameworks/AppKit.framework/AppKit"; + + [DllImport(AppKitFramework, CharSet = CharSet.Ansi)] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", + "CA2101:Specify marshaling for P/Invoke string arguments", + Justification = "objc_getClass method requires CharSet.Ansi to work.")] + public static extern IntPtr objc_getClass(string name); + + [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] + public static extern IntPtr objc_msgSend_retIntPtr(IntPtr target, IntPtr selector); + + [DllImport(AppKitFramework)] + public static extern IntPtr NSSelectorFromString(IntPtr cfstr); + + [DllImport(FoundationFramework)] + public static extern void CFRelease(IntPtr handle); + + [DllImport(FoundationFramework)] + public static extern IntPtr CFStringCreateWithBytes(IntPtr allocator, IntPtr buffer, + long bufferLength, CFStringEncoding encoding, bool isExternalRepresentation); + } + + [SuppressMessage("Usage", "CA2101: Specify marshaling for P/Invoke string arguments")] + [DllImport(AppKitFramework, CharSet = CharSet.Ansi)] + private static extern IntPtr objc_getClass(string name); + + [DllImport(FoundationFramework)] + private static extern IntPtr CFStringCreateWithBytes(IntPtr allocator, IntPtr buffer, + long bufferLength, MacOsTrashBindingHelper.CfStringEncoding encoding, + bool isExternalRepresentation); + + [SuppressMessage("Usage", "S6640: Make sure that using \"unsafe\" is safe here")] + internal static unsafe IntPtr CreateCfString(string aString) + { + var bytes = Encoding.Unicode.GetBytes(aString); + fixed ( byte* b = bytes ) + { + var cfStr = CFStringCreateWithBytes(IntPtr.Zero, ( IntPtr )b, bytes.Length, + MacOsTrashBindingHelper.CfStringEncoding.UTF16, false); + return cfStr; + } + } + + // let url = NSURL(fileURLWithPath: "/System/Applications/Utilities/Terminal.app", isDirectory: true) as URL + // + // let path = "/bin" + // let configuration = NSWorkspace.OpenConfiguration() + // configuration.arguments = [path] + // NSWorkspace.shared.openApplication(at: url, + // configuration: configuration, + // completionHandler: nil) + // NSWorkspace.shared.openFile + + private static IntPtr GetSelector(string name) + { + IntPtr cfstrSelector = CreateCFString(name); + IntPtr selector = NativeMethods.NSSelectorFromString(cfstrSelector); + NativeMethods.CFRelease(cfstrSelector); + return selector; + } + + private static unsafe IntPtr CreateCFString(string aString) + { + var bytes = Encoding.Unicode.GetBytes(aString); + fixed (byte* b = bytes) + { + var cfStr = NativeMethods.CFStringCreateWithBytes(IntPtr.Zero, (IntPtr)b, bytes.Length, CFStringEncoding.UTF16, false); + return cfStr; + } + } + + public static void Open() + { + var nsWorkspace = objc_getClass("NSWorkspace"); + var sharedWorkspace = + NativeMethods.objc_msgSend_retIntPtr(nsWorkspace, GetSelector("sharedWorkspace")); + + Console.WriteLine(); + } + + // static void Main(string[] args) + // { + // var cfStrTestFile = CreateCfString("/System/Applications/Utilities/Terminal.app"); + // var nsUrl = objc_getClass("NSURL"); + // var fileUrl = objc_msgSend_retIntPtr_IntPtr(nsUrl, GetSelector("fileURLWithPath:"), cfStrTestFile); + // CFRelease(cfStrTestFile); + // + // + // var url = NSUrl.FromFilename("/System/Applications/Utilities/Terminal.app"); + // var path = "/bin"; + // + // var configuration = new NSWorkspace.OpenConfiguration(); + // configuration.Arguments = new[] { path }; + // + // NSWorkspace.SharedWorkspace.OpenApplication(url, + // configuration, + // (success, error) => + // { + // if (error != null) + // { + // Console.WriteLine($"Error: {error.LocalizedDescription}"); + // } + // else if (!success) + // { + // Console.WriteLine("Failed to open application"); + // } + // else + // { + // Console.WriteLine("Application opened successfully"); + // } + // }); +} diff --git a/starsky/starsky.foundation.native/Trash/Helpers/MacOsTest2.cs b/starsky/starsky.foundation.native/Trash/Helpers/MacOsTest2.cs new file mode 100644 index 0000000000..583eafc11d --- /dev/null +++ b/starsky/starsky.foundation.native/Trash/Helpers/MacOsTest2.cs @@ -0,0 +1,76 @@ +using System.Runtime.InteropServices; + +namespace starsky.foundation.native.Trash.Helpers; + +// https://developer.apple.com/documentation/appkit/nsworkspace/3172700-openapplication +public class MacOsTest2 +{ + private const string FoundationFramework = "/System/Library/Frameworks/Foundation.framework/Foundation"; + + [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] + private static extern IntPtr objc_msgSend_retIntPtr(IntPtr target, IntPtr selector); + + [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] + private static extern void objc_msgSend_retVoid_IntPtr_IntPtr_IntPtr( + IntPtr target, + IntPtr selector, + IntPtr param1, + IntPtr param2, + IntPtr param3); + + [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] + private static extern IntPtr objc_msgSend_retIntPtr_IntPtr(IntPtr target, IntPtr selector, IntPtr param); + + public static void OpenApplicationAtURL( + string applicationURL) + { + var cfStrTestFile = MacOsTrashBindingHelper.CreateCfString(applicationURL); + var nsUrl = objc_getClass("NSURL"); + var fileUrl = objc_msgSend_retIntPtr_IntPtr(nsUrl, MacOsTrashBindingHelper.GetSelector("URLWithString:"), cfStrTestFile); + + var charArray = objc_msgSend(fileUrl, sel_registerName("absoluteURL")); + var test = Marshal.PtrToStringAnsi(charArray); + Console.WriteLine(test); + + var nsWorkspace = objc_getClass("NSWorkspace"); + var sharedWorkspace = objc_msgSend_retIntPtr(nsWorkspace, MacOsTrashBindingHelper.GetSelector("sharedWorkspace")); + var nsWorkspaceOpenConfiguration = objc_getClass("NSWorkspaceOpenConfiguration"); + var nsWorkspaceOpenConfigurationDefault = objc_msgSend_retIntPtr(nsWorkspaceOpenConfiguration, MacOsTrashBindingHelper.GetSelector("configuration")); + + objc_msgSend_retVoid_IntPtr_IntPtr_IntPtr( + sharedWorkspace, + MacOsTrashBindingHelper.GetSelector("openApplicationAtURL:configuration:completionHandler:"), + fileUrl, + nsWorkspaceOpenConfigurationDefault, + IntPtr.Zero); + } + + + public static string? GetText() + { + var nsString = objc_getClass("NSString"); + var nsPasteboard = objc_getClass("NSPasteboard"); + + var nsStringPboardType = objc_msgSend(objc_msgSend(nsString, sel_registerName("alloc")), sel_registerName("initWithUTF8String:"), "NSStringPboardType"); + var generalPasteboard = objc_msgSend(nsPasteboard, sel_registerName("generalPasteboard")); + var ptr = objc_msgSend(generalPasteboard, sel_registerName("stringForType:"), nsStringPboardType); + var charArray = objc_msgSend(ptr, sel_registerName("UTF8String")); + return Marshal.PtrToStringAnsi(charArray); + } + + [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] + static extern IntPtr objc_getClass(string className); + + [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] + static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector); + + [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] + static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector, string arg1); + + [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] + static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector, IntPtr arg1); + + [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] + static extern IntPtr sel_registerName(string selectorName); + +} diff --git a/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Test2Class.cs b/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Test2Class.cs new file mode 100644 index 0000000000..e5e5db6c3a --- /dev/null +++ b/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Test2Class.cs @@ -0,0 +1,17 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.native.Trash.Helpers; + +namespace starskytest.starsky.foundation.native.Trash.Helpers; + +[TestClass] +public class Test2Class +{ + [TestMethod] + public void TestMethod1() + { + MacOsTest2.OpenApplicationAtURL("/Applications/TextEdit.app"); + Console.WriteLine(); + + } +} From 459c11c7f5a414fd986b4c5d115d901d18c513cf Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 12 Feb 2024 19:37:51 +0100 Subject: [PATCH 002/125] it does something --- .../Trash/Helpers/MacOsOpenUrl.cs | 85 +++++++++++++++++++ .../Trash/Helpers/MacOsTest2.cs | 76 ----------------- .../Trash/Helpers/Example/Clipboard.cs | 36 ++++++++ .../Trash/Helpers/MacOsOpenUrlTests.cs | 33 +++++++ .../Trash/Helpers/Test2Class.cs | 6 +- 5 files changed, 158 insertions(+), 78 deletions(-) create mode 100644 starsky/starsky.foundation.native/Trash/Helpers/MacOsOpenUrl.cs delete mode 100644 starsky/starsky.foundation.native/Trash/Helpers/MacOsTest2.cs create mode 100644 starsky/starskytest/starsky.foundation.native/Trash/Helpers/Example/Clipboard.cs create mode 100644 starsky/starskytest/starsky.foundation.native/Trash/Helpers/MacOsOpenUrlTests.cs diff --git a/starsky/starsky.foundation.native/Trash/Helpers/MacOsOpenUrl.cs b/starsky/starsky.foundation.native/Trash/Helpers/MacOsOpenUrl.cs new file mode 100644 index 0000000000..3971a1f76c --- /dev/null +++ b/starsky/starsky.foundation.native/Trash/Helpers/MacOsOpenUrl.cs @@ -0,0 +1,85 @@ +using System.Runtime.InteropServices; + +namespace starsky.foundation.native.Trash.Helpers; + +public static class MacOsOpenUrl +{ + /// + /// Does NOT check if file exists + /// + /// Absolute Path of file + /// + public static bool OpenDefault( + string fileUrl) + { + var fileUrlIntPtr = + MacOsTrashBindingHelper.GetUrls([fileUrl]).FirstOrDefault(); + + return objc_msgSend_retBool_IntPtr_IntPtr( + NsWorkspaceSharedWorksPace(), + MacOsTrashBindingHelper.GetSelector("openURL:"), + fileUrlIntPtr); + } + + public static void OpenApplicationAtUrl( + string fileUrl, + string applicationUrl) + { + var fileUrlIntPtr = MacOsTrashBindingHelper.GetUrls([fileUrl]); + var fileUrlIntPtrUrlArray = MacOsTrashBindingHelper.CreateCfArray(fileUrlIntPtr); + + var applicationUrlIntPtr = + MacOsTrashBindingHelper.GetUrls([applicationUrl]).FirstOrDefault(); + + var nsWorkspaceOpenConfiguration = objc_getClass("NSWorkspaceOpenConfiguration"); + var nsWorkspaceOpenConfigurationDefault = objc_msgSend_retIntPtr( + nsWorkspaceOpenConfiguration, MacOsTrashBindingHelper.GetSelector("configuration")); + + // https://developer.apple.com/documentation/appkit/nsworkspace/3172702-openurls?language=objc + objc_msgSend_retVoid_IntPtr_IntPtr_IntPtr_IntPtr( + NsWorkspaceSharedWorksPace(), + MacOsTrashBindingHelper.GetSelector("openURLs:withApplicationAtURL:configuration:completionHandler:"), + fileUrlIntPtrUrlArray, + applicationUrlIntPtr, + nsWorkspaceOpenConfigurationDefault, + IntPtr.Zero); + } + + private const string FoundationFramework = + "/System/Library/Frameworks/Foundation.framework/Foundation"; + + [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] + private static extern IntPtr objc_msgSend_retIntPtr(IntPtr target, IntPtr selector); + + [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] + private static extern IntPtr objc_msgSend_retVoid_IntPtr_IntPtr_IntPtr_IntPtr( + IntPtr target, + IntPtr selector, + IntPtr param1, + IntPtr param2, + IntPtr param3, + IntPtr param4); + + [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] + private static extern IntPtr objc_msgSend_retVoid_IntPtr( + IntPtr target, + IntPtr selector, + IntPtr param1); + + [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] + static extern IntPtr objc_getClass(string className); + + [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] + private static extern bool objc_msgSend_retBool_IntPtr_IntPtr(IntPtr target, IntPtr selector, IntPtr param); + + + + private static IntPtr NsWorkspaceSharedWorksPace() + { + // Namespace + var nsWorkspace = objc_getClass("NSWorkspace"); + return objc_msgSend_retIntPtr(nsWorkspace, + MacOsTrashBindingHelper.GetSelector("sharedWorkspace")); + } + +} diff --git a/starsky/starsky.foundation.native/Trash/Helpers/MacOsTest2.cs b/starsky/starsky.foundation.native/Trash/Helpers/MacOsTest2.cs deleted file mode 100644 index 583eafc11d..0000000000 --- a/starsky/starsky.foundation.native/Trash/Helpers/MacOsTest2.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Runtime.InteropServices; - -namespace starsky.foundation.native.Trash.Helpers; - -// https://developer.apple.com/documentation/appkit/nsworkspace/3172700-openapplication -public class MacOsTest2 -{ - private const string FoundationFramework = "/System/Library/Frameworks/Foundation.framework/Foundation"; - - [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] - private static extern IntPtr objc_msgSend_retIntPtr(IntPtr target, IntPtr selector); - - [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] - private static extern void objc_msgSend_retVoid_IntPtr_IntPtr_IntPtr( - IntPtr target, - IntPtr selector, - IntPtr param1, - IntPtr param2, - IntPtr param3); - - [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] - private static extern IntPtr objc_msgSend_retIntPtr_IntPtr(IntPtr target, IntPtr selector, IntPtr param); - - public static void OpenApplicationAtURL( - string applicationURL) - { - var cfStrTestFile = MacOsTrashBindingHelper.CreateCfString(applicationURL); - var nsUrl = objc_getClass("NSURL"); - var fileUrl = objc_msgSend_retIntPtr_IntPtr(nsUrl, MacOsTrashBindingHelper.GetSelector("URLWithString:"), cfStrTestFile); - - var charArray = objc_msgSend(fileUrl, sel_registerName("absoluteURL")); - var test = Marshal.PtrToStringAnsi(charArray); - Console.WriteLine(test); - - var nsWorkspace = objc_getClass("NSWorkspace"); - var sharedWorkspace = objc_msgSend_retIntPtr(nsWorkspace, MacOsTrashBindingHelper.GetSelector("sharedWorkspace")); - var nsWorkspaceOpenConfiguration = objc_getClass("NSWorkspaceOpenConfiguration"); - var nsWorkspaceOpenConfigurationDefault = objc_msgSend_retIntPtr(nsWorkspaceOpenConfiguration, MacOsTrashBindingHelper.GetSelector("configuration")); - - objc_msgSend_retVoid_IntPtr_IntPtr_IntPtr( - sharedWorkspace, - MacOsTrashBindingHelper.GetSelector("openApplicationAtURL:configuration:completionHandler:"), - fileUrl, - nsWorkspaceOpenConfigurationDefault, - IntPtr.Zero); - } - - - public static string? GetText() - { - var nsString = objc_getClass("NSString"); - var nsPasteboard = objc_getClass("NSPasteboard"); - - var nsStringPboardType = objc_msgSend(objc_msgSend(nsString, sel_registerName("alloc")), sel_registerName("initWithUTF8String:"), "NSStringPboardType"); - var generalPasteboard = objc_msgSend(nsPasteboard, sel_registerName("generalPasteboard")); - var ptr = objc_msgSend(generalPasteboard, sel_registerName("stringForType:"), nsStringPboardType); - var charArray = objc_msgSend(ptr, sel_registerName("UTF8String")); - return Marshal.PtrToStringAnsi(charArray); - } - - [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] - static extern IntPtr objc_getClass(string className); - - [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] - static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector); - - [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] - static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector, string arg1); - - [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] - static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector, IntPtr arg1); - - [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] - static extern IntPtr sel_registerName(string selectorName); - -} diff --git a/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Example/Clipboard.cs b/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Example/Clipboard.cs new file mode 100644 index 0000000000..143b4689b0 --- /dev/null +++ b/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Example/Clipboard.cs @@ -0,0 +1,36 @@ +using System; +using System.Runtime.InteropServices; + +namespace starskytest.starsky.foundation.native.Trash.Helpers.Example; + +public static class Clipboard +{ + [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] + static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector); + + [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] + static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector, string arg1); + + [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] + static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector, IntPtr arg1); + + [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] + static extern IntPtr sel_registerName(string selectorName); + + [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] + static extern IntPtr objc_getClass(string className); + + public static string? GetText() + { + var nsString = objc_getClass("NSString"); + var nsPasteboard = objc_getClass("NSPasteboard"); + + var nsStringPboardType = objc_msgSend(objc_msgSend(nsString, sel_registerName("alloc")), + sel_registerName("initWithUTF8String:"), "NSStringPboardType"); + var generalPasteboard = objc_msgSend(nsPasteboard, sel_registerName("generalPasteboard")); + var ptr = objc_msgSend(generalPasteboard, sel_registerName("stringForType:"), + nsStringPboardType); + var charArray = objc_msgSend(ptr, sel_registerName("UTF8String")); + return Marshal.PtrToStringAnsi(charArray); + } +} diff --git a/starsky/starskytest/starsky.foundation.native/Trash/Helpers/MacOsOpenUrlTests.cs b/starsky/starskytest/starsky.foundation.native/Trash/Helpers/MacOsOpenUrlTests.cs new file mode 100644 index 0000000000..57dce09109 --- /dev/null +++ b/starsky/starskytest/starsky.foundation.native/Trash/Helpers/MacOsOpenUrlTests.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.native.Trash.Helpers; +using starskytest.FakeCreateAn; + +namespace starskytest.starsky.foundation.native.Trash.Helpers; + +[TestClass] +public class MacOsOpenUrlTests +{ + [TestMethod] + public void TestMethodWithSpecificApp() + { + var filePath = new CreateAnImage().FullFilePath; + + MacOsOpenUrl.OpenApplicationAtUrl(filePath, + "/System/Applications/Preview.app"); + + Thread.Sleep(1000); + Console.WriteLine(); + } + + [TestMethod] + public void TestMethodWithDefaultApp() + { + var filePath = new CreateAnImage().FullFilePath; + + MacOsOpenUrl.OpenDefault(filePath); + + Thread.Sleep(1000); + } +} diff --git a/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Test2Class.cs b/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Test2Class.cs index e5e5db6c3a..de0c4d64b0 100644 --- a/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Test2Class.cs +++ b/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Test2Class.cs @@ -10,8 +10,10 @@ public class Test2Class [TestMethod] public void TestMethod1() { - MacOsTest2.OpenApplicationAtURL("/Applications/TextEdit.app"); - Console.WriteLine(); + MacOsOpenUrl.OpenApplicationAtUrl("/Users/dion/Desktop/rosseta.png", + "/Applications/Adobe Photoshop 2024/Adobe Photoshop 2024.app"); + // MacOsTest2.OpenDefault("/Users/dion/Desktop/rosseta.png"); + Console.WriteLine(); } } From 2c15408b55441ebc8dd3e416d0c3c0c818e484d1 Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 12 Feb 2024 20:29:02 +0100 Subject: [PATCH 003/125] Add tests --- .../Helpers/MacOsOpenUrl.cs | 34 ++--- .../OpenApplicationNativeService.cs | 6 + .../Trash/Helpers/MacOSTrashBindingHelper.cs | 5 + .../Trash/Helpers/MacOsLaunch.cs | 120 --------------- .../Trash/Helpers/MacOsOpen.cs | 142 ------------------ .../Trash/Helpers/MacOsOpenUrlTests.cs | 36 +++-- .../Trash/Helpers/Test2Class.cs | 3 +- 7 files changed, 54 insertions(+), 292 deletions(-) rename starsky/starsky.foundation.native/{Trash => OpenApplicationNative}/Helpers/MacOsOpenUrl.cs (83%) create mode 100644 starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs delete mode 100644 starsky/starsky.foundation.native/Trash/Helpers/MacOsLaunch.cs delete mode 100644 starsky/starsky.foundation.native/Trash/Helpers/MacOsOpen.cs diff --git a/starsky/starsky.foundation.native/Trash/Helpers/MacOsOpenUrl.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs similarity index 83% rename from starsky/starsky.foundation.native/Trash/Helpers/MacOsOpenUrl.cs rename to starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs index 3971a1f76c..a30cb9039a 100644 --- a/starsky/starsky.foundation.native/Trash/Helpers/MacOsOpenUrl.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs @@ -1,9 +1,11 @@ using System.Runtime.InteropServices; +using starsky.foundation.native.Trash.Helpers; -namespace starsky.foundation.native.Trash.Helpers; +namespace starsky.foundation.native.OpenApplicationNative.Helpers; public static class MacOsOpenUrl { + /// /// Does NOT check if file exists /// @@ -14,13 +16,18 @@ public static bool OpenDefault( { var fileUrlIntPtr = MacOsTrashBindingHelper.GetUrls([fileUrl]).FirstOrDefault(); - + return objc_msgSend_retBool_IntPtr_IntPtr( NsWorkspaceSharedWorksPace(), MacOsTrashBindingHelper.GetSelector("openURL:"), fileUrlIntPtr); } + /// + /// Does NOT check if a file exists + /// + /// Absolute Path + /// Open with .app folder public static void OpenApplicationAtUrl( string fileUrl, string applicationUrl) @@ -34,17 +41,18 @@ public static void OpenApplicationAtUrl( var nsWorkspaceOpenConfiguration = objc_getClass("NSWorkspaceOpenConfiguration"); var nsWorkspaceOpenConfigurationDefault = objc_msgSend_retIntPtr( nsWorkspaceOpenConfiguration, MacOsTrashBindingHelper.GetSelector("configuration")); - + // https://developer.apple.com/documentation/appkit/nsworkspace/3172702-openurls?language=objc objc_msgSend_retVoid_IntPtr_IntPtr_IntPtr_IntPtr( NsWorkspaceSharedWorksPace(), - MacOsTrashBindingHelper.GetSelector("openURLs:withApplicationAtURL:configuration:completionHandler:"), + MacOsTrashBindingHelper.GetSelector( + "openURLs:withApplicationAtURL:configuration:completionHandler:"), fileUrlIntPtrUrlArray, applicationUrlIntPtr, nsWorkspaceOpenConfigurationDefault, IntPtr.Zero); } - + private const string FoundationFramework = "/System/Library/Frameworks/Foundation.framework/Foundation"; @@ -59,20 +67,13 @@ private static extern IntPtr objc_msgSend_retVoid_IntPtr_IntPtr_IntPtr_IntPtr( IntPtr param2, IntPtr param3, IntPtr param4); - - [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] - private static extern IntPtr objc_msgSend_retVoid_IntPtr( - IntPtr target, - IntPtr selector, - IntPtr param1); - + [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] static extern IntPtr objc_getClass(string className); - - [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] - private static extern bool objc_msgSend_retBool_IntPtr_IntPtr(IntPtr target, IntPtr selector, IntPtr param); - + [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] + private static extern bool objc_msgSend_retBool_IntPtr_IntPtr(IntPtr target, IntPtr selector, + IntPtr param); private static IntPtr NsWorkspaceSharedWorksPace() { @@ -81,5 +82,4 @@ private static IntPtr NsWorkspaceSharedWorksPace() return objc_msgSend_retIntPtr(nsWorkspace, MacOsTrashBindingHelper.GetSelector("sharedWorkspace")); } - } diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs b/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs new file mode 100644 index 0000000000..7ee433bf97 --- /dev/null +++ b/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs @@ -0,0 +1,6 @@ +namespace starsky.foundation.native.OpenApplicationNative; + +public class OpenApplicationNativeService +{ + +} diff --git a/starsky/starsky.foundation.native/Trash/Helpers/MacOSTrashBindingHelper.cs b/starsky/starsky.foundation.native/Trash/Helpers/MacOSTrashBindingHelper.cs index 93e54f568c..5a6ed0957c 100644 --- a/starsky/starsky.foundation.native/Trash/Helpers/MacOSTrashBindingHelper.cs +++ b/starsky/starsky.foundation.native/Trash/Helpers/MacOSTrashBindingHelper.cs @@ -79,6 +79,11 @@ internal static void TrashInternal(List filesFullPath) // CFRelease the fileUrl, sharedWorkspace, nsWorkspace gives a crash (error 139) } + /// + /// Get Selector in the Objective-C runtime + /// + /// Name + /// Object internal static IntPtr GetSelector(string name) { var cfStrSelector = CreateCfString(name); diff --git a/starsky/starsky.foundation.native/Trash/Helpers/MacOsLaunch.cs b/starsky/starsky.foundation.native/Trash/Helpers/MacOsLaunch.cs deleted file mode 100644 index f554710886..0000000000 --- a/starsky/starsky.foundation.native/Trash/Helpers/MacOsLaunch.cs +++ /dev/null @@ -1,120 +0,0 @@ -// using System.Runtime.InteropServices; -// -// namespace starsky.foundation.native.Trash.Helpers; -// -// public class MacOsLaunch -// { -// private const string FoundationFramework = "/System/Library/Frameworks/Foundation.framework/Foundation"; -// private const string AppKitFramework = "/System/Library/Frameworks/AppKit.framework/AppKit"; -// -// [DllImport(AppKitFramework)] -// private static extern IntPtr NSSelectorFromString(IntPtr cfstr); -// -// [DllImport(FoundationFramework)] -// private static extern void CFRelease(IntPtr handle); -// -// public class ApplicationStartInfo -// { -// public ApplicationStartInfo (string application) -// { -// this.Application = application; -// this.Environment = new Dictionary (); -// } -// -// public string Application { get; set; } -// public Dictionary Environment { get; private set; } -// public string[] Args { get; set; } -// public bool Async { get; set; } -// public bool NewInstance { get; set; } -// } -// -// -// public static class LaunchServices -// { -// public static int OpenApplication (string application) -// { -// return OpenApplication (new ApplicationStartInfo (application)); -// } -// -// internal static IntPtr GetSelector(string name) -// { -// var cfStrSelector = MacOsTrashBindingHelper.CreateCfString(name); -// var selector = NSSelectorFromString(cfStrSelector); -// CFRelease(cfStrSelector); -// return selector; -// } -// -// // This function can be replaced by NSWorkspace.LaunchApplication but it currently doesn't work -// // https://bugzilla.xamarin.com/show_bug.cgi?id=32540 -// -// [DllImport ("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")] -// static extern IntPtr IntPtr_objc_msgSend_IntPtr_UInt32_IntPtr_IntPtr (IntPtr receiver, IntPtr selector, IntPtr url, UInt32 options, IntPtr configuration, out IntPtr error); -// static readonly IntPtr launchApplicationAtURLOptionsConfigurationErrorSelector = ObjCRuntime.Selector.GetHandle ("launchApplicationAtURL:options:configuration:error:"); -// public static int OpenApplication (ApplicationStartInfo application) -// { -// if (application == null) -// throw new ArgumentNullException ("application"); -// -// if (string.IsNullOrEmpty (application.Application) || !Directory.Exists (application.Application)) -// throw new ArgumentException ("Application is not valid"); -// -// NSUrl appUrl = NSUrl.FromFilename (application.Application); -// -// // TODO: Once the above bug is fixed, we can replace the code below with -// //NSRunningApplication app = NSWorkspace.SharedWorkspace.LaunchApplication (appUrl, 0, new NSDictionary (), null); -// -// var config = new NSMutableDictionary (); -// if (application.Args != null && application.Args.Length > 0) { -// var args = new NSMutableArray (); -// foreach (string arg in application.Args) { -// args.Add (new NSString (arg)); -// } -// config.Add (new NSString ("NSWorkspaceLaunchConfigurationArguments"), args); -// } -// -// if (application.Environment != null && application.Environment.Count > 0) { -// var envValueStrings = application.Environment.Values.Select (t => new NSString (t)).ToArray (); -// var envKeyStrings = application.Environment.Keys.Select (t => new NSString (t)).ToArray (); -// -// var envDict = new NSMutableDictionary (); -// for (int i = 0; i < envValueStrings.Length; i++) { -// envDict.Add (envKeyStrings[i], envValueStrings[i]); -// } -// -// config.Add (new NSString ("NSWorkspaceLaunchConfigurationEnvironment"), envDict); -// } -// -// UInt32 options = 0; -// -// if (application.Async) -// options |= (UInt32) LaunchOptions.NSWorkspaceLaunchAsync; -// if (application.NewInstance) -// options |= (UInt32) LaunchOptions.NSWorkspaceLaunchNewInstance; -// -// IntPtr error; -// var appHandle = IntPtr_objc_msgSend_IntPtr_UInt32_IntPtr_IntPtr (NSWorkspace.SharedWorkspace.Handle, launchApplicationAtURLOptionsConfigurationErrorSelector, appUrl.Handle, options, config.Handle, out error); -// if (appHandle == IntPtr.Zero) -// return -1; -// -// NSRunningApplication app = (NSRunningApplication)ObjCRuntime.Runtime.GetNSObject (appHandle); -// -// return app.ProcessIdentifier; -// } -// -// [Flags] -// enum LaunchOptions { -// NSWorkspaceLaunchAndPrint = 0x00000002, -// NSWorkspaceLaunchWithErrorPresentation = 0x00000040, -// NSWorkspaceLaunchInhibitingBackgroundOnly = 0x00000080, -// NSWorkspaceLaunchWithoutAddingToRecents = 0x00000100, -// NSWorkspaceLaunchWithoutActivation = 0x00000200, -// NSWorkspaceLaunchAsync = 0x00010000, -// NSWorkspaceLaunchAllowingClassicStartup = 0x00020000, -// NSWorkspaceLaunchPreferringClassic = 0x00040000, -// NSWorkspaceLaunchNewInstance = 0x00080000, -// NSWorkspaceLaunchAndHide = 0x00100000, -// NSWorkspaceLaunchAndHideOthers = 0x00200000, -// NSWorkspaceLaunchDefault = NSWorkspaceLaunchAsync | NSWorkspaceLaunchAllowingClassicStartup -// }; -// } -// } diff --git a/starsky/starsky.foundation.native/Trash/Helpers/MacOsOpen.cs b/starsky/starsky.foundation.native/Trash/Helpers/MacOsOpen.cs deleted file mode 100644 index 9e9bceb6ad..0000000000 --- a/starsky/starsky.foundation.native/Trash/Helpers/MacOsOpen.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; -using System.Text; - -namespace starsky.foundation.native.Trash.Helpers; - -public class MacOsOpen -{ - private const string AppKitFramework = "/System/Library/Frameworks/AppKit.framework/AppKit"; - - private const string FoundationFramework = - "/System/Library/Frameworks/Foundation.framework/Foundation"; - - - private enum CFStringEncoding : uint - { - UTF16 = 0x0100, - UTF16BE = 0x10000100, - UTF16LE = 0x14000100, - ASCII = 0x0600 - } - - /// - /// Native methods we can call on the Mac. - /// - private static class NativeMethods - { - private const string FoundationFramework = - "/System/Library/Frameworks/Foundation.framework/Foundation"; - - private const string AppKitFramework = "/System/Library/Frameworks/AppKit.framework/AppKit"; - - [DllImport(AppKitFramework, CharSet = CharSet.Ansi)] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", - "CA2101:Specify marshaling for P/Invoke string arguments", - Justification = "objc_getClass method requires CharSet.Ansi to work.")] - public static extern IntPtr objc_getClass(string name); - - [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] - public static extern IntPtr objc_msgSend_retIntPtr(IntPtr target, IntPtr selector); - - [DllImport(AppKitFramework)] - public static extern IntPtr NSSelectorFromString(IntPtr cfstr); - - [DllImport(FoundationFramework)] - public static extern void CFRelease(IntPtr handle); - - [DllImport(FoundationFramework)] - public static extern IntPtr CFStringCreateWithBytes(IntPtr allocator, IntPtr buffer, - long bufferLength, CFStringEncoding encoding, bool isExternalRepresentation); - } - - [SuppressMessage("Usage", "CA2101: Specify marshaling for P/Invoke string arguments")] - [DllImport(AppKitFramework, CharSet = CharSet.Ansi)] - private static extern IntPtr objc_getClass(string name); - - [DllImport(FoundationFramework)] - private static extern IntPtr CFStringCreateWithBytes(IntPtr allocator, IntPtr buffer, - long bufferLength, MacOsTrashBindingHelper.CfStringEncoding encoding, - bool isExternalRepresentation); - - [SuppressMessage("Usage", "S6640: Make sure that using \"unsafe\" is safe here")] - internal static unsafe IntPtr CreateCfString(string aString) - { - var bytes = Encoding.Unicode.GetBytes(aString); - fixed ( byte* b = bytes ) - { - var cfStr = CFStringCreateWithBytes(IntPtr.Zero, ( IntPtr )b, bytes.Length, - MacOsTrashBindingHelper.CfStringEncoding.UTF16, false); - return cfStr; - } - } - - // let url = NSURL(fileURLWithPath: "/System/Applications/Utilities/Terminal.app", isDirectory: true) as URL - // - // let path = "/bin" - // let configuration = NSWorkspace.OpenConfiguration() - // configuration.arguments = [path] - // NSWorkspace.shared.openApplication(at: url, - // configuration: configuration, - // completionHandler: nil) - // NSWorkspace.shared.openFile - - private static IntPtr GetSelector(string name) - { - IntPtr cfstrSelector = CreateCFString(name); - IntPtr selector = NativeMethods.NSSelectorFromString(cfstrSelector); - NativeMethods.CFRelease(cfstrSelector); - return selector; - } - - private static unsafe IntPtr CreateCFString(string aString) - { - var bytes = Encoding.Unicode.GetBytes(aString); - fixed (byte* b = bytes) - { - var cfStr = NativeMethods.CFStringCreateWithBytes(IntPtr.Zero, (IntPtr)b, bytes.Length, CFStringEncoding.UTF16, false); - return cfStr; - } - } - - public static void Open() - { - var nsWorkspace = objc_getClass("NSWorkspace"); - var sharedWorkspace = - NativeMethods.objc_msgSend_retIntPtr(nsWorkspace, GetSelector("sharedWorkspace")); - - Console.WriteLine(); - } - - // static void Main(string[] args) - // { - // var cfStrTestFile = CreateCfString("/System/Applications/Utilities/Terminal.app"); - // var nsUrl = objc_getClass("NSURL"); - // var fileUrl = objc_msgSend_retIntPtr_IntPtr(nsUrl, GetSelector("fileURLWithPath:"), cfStrTestFile); - // CFRelease(cfStrTestFile); - // - // - // var url = NSUrl.FromFilename("/System/Applications/Utilities/Terminal.app"); - // var path = "/bin"; - // - // var configuration = new NSWorkspace.OpenConfiguration(); - // configuration.Arguments = new[] { path }; - // - // NSWorkspace.SharedWorkspace.OpenApplication(url, - // configuration, - // (success, error) => - // { - // if (error != null) - // { - // Console.WriteLine($"Error: {error.LocalizedDescription}"); - // } - // else if (!success) - // { - // Console.WriteLine("Failed to open application"); - // } - // else - // { - // Console.WriteLine("Application opened successfully"); - // } - // }); -} diff --git a/starsky/starskytest/starsky.foundation.native/Trash/Helpers/MacOsOpenUrlTests.cs b/starsky/starskytest/starsky.foundation.native/Trash/Helpers/MacOsOpenUrlTests.cs index 57dce09109..e29a50b5eb 100644 --- a/starsky/starskytest/starsky.foundation.native/Trash/Helpers/MacOsOpenUrlTests.cs +++ b/starsky/starskytest/starsky.foundation.native/Trash/Helpers/MacOsOpenUrlTests.cs @@ -1,7 +1,10 @@ using System; +using System.Diagnostics; using System.Threading; +using System.Threading.Tasks; +using Medallion.Shell; using Microsoft.VisualStudio.TestTools.UnitTesting; -using starsky.foundation.native.Trash.Helpers; +using starsky.foundation.native.OpenApplicationNative.Helpers; using starskytest.FakeCreateAn; namespace starskytest.starsky.foundation.native.Trash.Helpers; @@ -10,24 +13,33 @@ namespace starskytest.starsky.foundation.native.Trash.Helpers; public class MacOsOpenUrlTests { [TestMethod] - public void TestMethodWithSpecificApp() + public async Task TestMethodWithSpecificApp() { var filePath = new CreateAnImage().FullFilePath; - + MacOsOpenUrl.OpenApplicationAtUrl(filePath, - "/System/Applications/Preview.app"); + "/System/Applications/Utilities/Console.app"); + + var isProcess = Process.GetProcessesByName("Console").Length > 0; + for ( var i = 0; i < 10; i++ ) + { + isProcess = Process.GetProcessesByName("Console").Length > 0; + if ( isProcess ) + { + await Command.Run("osascript", "-e", "tell application \"Console\" to if it is running then quit").Task; + break; + } + + await Task.Delay(10); + } - Thread.Sleep(1000); - Console.WriteLine(); + Assert.IsTrue(isProcess); } - + [TestMethod] public void TestMethodWithDefaultApp() { - var filePath = new CreateAnImage().FullFilePath; - - MacOsOpenUrl.OpenDefault(filePath); - - Thread.Sleep(1000); + var result = MacOsOpenUrl.OpenDefault("urlNotFound"); + Assert.IsFalse(result); } } diff --git a/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Test2Class.cs b/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Test2Class.cs index de0c4d64b0..ea5ba7481c 100644 --- a/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Test2Class.cs +++ b/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Test2Class.cs @@ -1,5 +1,6 @@ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.native.OpenApplicationNative.Helpers; using starsky.foundation.native.Trash.Helpers; namespace starskytest.starsky.foundation.native.Trash.Helpers; @@ -12,7 +13,7 @@ public void TestMethod1() { MacOsOpenUrl.OpenApplicationAtUrl("/Users/dion/Desktop/rosseta.png", "/Applications/Adobe Photoshop 2024/Adobe Photoshop 2024.app"); - + // MacOsTest2.OpenDefault("/Users/dion/Desktop/rosseta.png"); Console.WriteLine(); } From c83e19bbdddcef17fbb9b0c4d56d71a598b01ab4 Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 12 Feb 2024 22:47:48 +0100 Subject: [PATCH 004/125] try this --- .../Helpers/MacOsOpenUrl.cs | 2 +- .../Helpers/OpenDefaultApp.bak | 40 ++++++++++++++++ .../Helpers/MacOsOpenUrlTests.cs | 46 +++++++++++++++++++ .../Helpers/OpenDefaultAppTests.bak | 19 ++++++++ .../Trash/Helpers/MacOsOpenUrlTests.cs | 45 ------------------ .../Trash/Helpers/Test2Class.cs | 20 -------- 6 files changed, 106 insertions(+), 66 deletions(-) create mode 100644 starsky/starsky.foundation.native/OpenApplicationNative/Helpers/OpenDefaultApp.bak create mode 100644 starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs create mode 100644 starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/OpenDefaultAppTests.bak delete mode 100644 starsky/starskytest/starsky.foundation.native/Trash/Helpers/MacOsOpenUrlTests.cs delete mode 100644 starsky/starskytest/starsky.foundation.native/Trash/Helpers/Test2Class.cs diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs index a30cb9039a..0339fca47a 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs @@ -75,7 +75,7 @@ private static extern IntPtr objc_msgSend_retVoid_IntPtr_IntPtr_IntPtr_IntPtr( private static extern bool objc_msgSend_retBool_IntPtr_IntPtr(IntPtr target, IntPtr selector, IntPtr param); - private static IntPtr NsWorkspaceSharedWorksPace() + internal static IntPtr NsWorkspaceSharedWorksPace() { // Namespace var nsWorkspace = objc_getClass("NSWorkspace"); diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/OpenDefaultApp.bak b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/OpenDefaultApp.bak new file mode 100644 index 0000000000..be0727ac0f --- /dev/null +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/OpenDefaultApp.bak @@ -0,0 +1,40 @@ +using System.Diagnostics.CodeAnalysis; +using starsky.foundation.native.Trash.Helpers; + +namespace starsky.foundation.native.OpenApplicationNative.Helpers; + +using System; +using System.Runtime.InteropServices; + +public class MacOsOpenDefaultApp +{ + public static void SetDefaultApplicationAtURL( + string applicationURL, + string fileURL) + { + var nsUrl = MacOsTrashBindingHelper.GetUrls([applicationURL]).FirstOrDefault(); + var fileUrl = MacOsTrashBindingHelper.GetUrls([fileURL]).FirstOrDefault(); + + objc_msgSend_retVoid_IntPtr_IntPtr_IntPtr( + MacOsOpenUrl.NsWorkspaceSharedWorksPace(), + MacOsTrashBindingHelper.GetSelector( + "setDefaultApplicationAtURL:toOpenFileAtURL:completionHandler:"), + nsUrl, + fileUrl, + IntPtr.Zero); + } + + private const string FoundationFramework = + "/System/Library/Frameworks/Foundation.framework/Foundation"; + + [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] + private static extern void objc_msgSend_retVoid_IntPtr_IntPtr_IntPtr( + IntPtr target, + IntPtr selector, + IntPtr param1, + IntPtr param2, + IntPtr param3); + + + +} diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs new file mode 100644 index 0000000000..fbe555ab4b --- /dev/null +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs @@ -0,0 +1,46 @@ +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Medallion.Shell; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.native.OpenApplicationNative.Helpers; +using starskytest.FakeCreateAn; + +namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers; + +[TestClass] +public class MacOsOpenUrlTests +{ + private const string ConsoleApp = "/System/Applications/Utilities/Console.app"; + private const string ConsoleName = "Console"; + + [TestMethod] + public async Task TestMethodWithSpecificApp() + { + var filePath = new CreateAnImage().FullFilePath; + + MacOsOpenUrl.OpenApplicationAtUrl(filePath, ConsoleApp); + + var isProcess = Process.GetProcessesByName(ConsoleName).Any(p => p.MainModule?.FileName.Contains(ConsoleApp) == true); + for (var i = 0; i < 15; i++) + { + isProcess = Process.GetProcessesByName(ConsoleName).Any(p => p.MainModule?.FileName.Contains(ConsoleApp) == true); + if (isProcess) + { + await Command.Run("osascript", "-e", "tell application \"Console\" to if it is running then quit").Task; + break; + } + + await Task.Delay(5); + } + + Assert.IsTrue(isProcess); + } + + [TestMethod] + public void TestMethodWithDefaultApp() + { + var result = MacOsOpenUrl.OpenDefault("urlNotFound"); + Assert.IsFalse(result); + } +} diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/OpenDefaultAppTests.bak b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/OpenDefaultAppTests.bak new file mode 100644 index 0000000000..fde2387ff9 --- /dev/null +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/OpenDefaultAppTests.bak @@ -0,0 +1,19 @@ +using System; +using System.Threading; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.native.OpenApplicationNative.Helpers; + +namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers; + +[TestClass] +public class OpenDefaultAppTests +{ + private const string ConsoleApp = "/System/Applications/Utilities/Console.app"; + + [TestMethod] + public void TEst() + { + MacOsOpenDefaultApp.SetDefaultApplicationAtURL(ConsoleApp,"/Users/dion/Desktop/mariadb.yml"); + Thread.Sleep(1000); + } +} diff --git a/starsky/starskytest/starsky.foundation.native/Trash/Helpers/MacOsOpenUrlTests.cs b/starsky/starskytest/starsky.foundation.native/Trash/Helpers/MacOsOpenUrlTests.cs deleted file mode 100644 index e29a50b5eb..0000000000 --- a/starsky/starskytest/starsky.foundation.native/Trash/Helpers/MacOsOpenUrlTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Medallion.Shell; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using starsky.foundation.native.OpenApplicationNative.Helpers; -using starskytest.FakeCreateAn; - -namespace starskytest.starsky.foundation.native.Trash.Helpers; - -[TestClass] -public class MacOsOpenUrlTests -{ - [TestMethod] - public async Task TestMethodWithSpecificApp() - { - var filePath = new CreateAnImage().FullFilePath; - - MacOsOpenUrl.OpenApplicationAtUrl(filePath, - "/System/Applications/Utilities/Console.app"); - - var isProcess = Process.GetProcessesByName("Console").Length > 0; - for ( var i = 0; i < 10; i++ ) - { - isProcess = Process.GetProcessesByName("Console").Length > 0; - if ( isProcess ) - { - await Command.Run("osascript", "-e", "tell application \"Console\" to if it is running then quit").Task; - break; - } - - await Task.Delay(10); - } - - Assert.IsTrue(isProcess); - } - - [TestMethod] - public void TestMethodWithDefaultApp() - { - var result = MacOsOpenUrl.OpenDefault("urlNotFound"); - Assert.IsFalse(result); - } -} diff --git a/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Test2Class.cs b/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Test2Class.cs deleted file mode 100644 index ea5ba7481c..0000000000 --- a/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Test2Class.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using starsky.foundation.native.OpenApplicationNative.Helpers; -using starsky.foundation.native.Trash.Helpers; - -namespace starskytest.starsky.foundation.native.Trash.Helpers; - -[TestClass] -public class Test2Class -{ - [TestMethod] - public void TestMethod1() - { - MacOsOpenUrl.OpenApplicationAtUrl("/Users/dion/Desktop/rosseta.png", - "/Applications/Adobe Photoshop 2024/Adobe Photoshop 2024.app"); - - // MacOsTest2.OpenDefault("/Users/dion/Desktop/rosseta.png"); - Console.WriteLine(); - } -} From 97a9de34b7332fa44b2f9fbdf3944bfb7f6d3a25 Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 12 Feb 2024 22:50:03 +0100 Subject: [PATCH 005/125] rm --- .../starskydesktop.xcodeproj/project.pbxproj | 578 ------------------ .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../AccentColor.colorset/Contents.json | 11 - .../AppIcon.appiconset/Contents.json | 58 -- .../Assets.xcassets/Contents.json | 6 - .../starskydesktop/ContentView.swift | 24 - .../Preview Assets.xcassets/Contents.json | 6 - .../starskydesktop.entitlements | 10 - .../starskydesktop/starskydesktopApp.swift | 17 - .../starskydesktopTests.swift | 36 -- .../starskydesktopUITests.swift | 41 -- .../starskydesktopUITestsLaunchTests.swift | 32 - 13 files changed, 834 deletions(-) delete mode 100644 desktopmac/starskydesktop/starskydesktop.xcodeproj/project.pbxproj delete mode 100644 desktopmac/starskydesktop/starskydesktop.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 desktopmac/starskydesktop/starskydesktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 desktopmac/starskydesktop/starskydesktop/Assets.xcassets/AccentColor.colorset/Contents.json delete mode 100644 desktopmac/starskydesktop/starskydesktop/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 desktopmac/starskydesktop/starskydesktop/Assets.xcassets/Contents.json delete mode 100644 desktopmac/starskydesktop/starskydesktop/ContentView.swift delete mode 100644 desktopmac/starskydesktop/starskydesktop/Preview Content/Preview Assets.xcassets/Contents.json delete mode 100644 desktopmac/starskydesktop/starskydesktop/starskydesktop.entitlements delete mode 100644 desktopmac/starskydesktop/starskydesktop/starskydesktopApp.swift delete mode 100644 desktopmac/starskydesktop/starskydesktopTests/starskydesktopTests.swift delete mode 100644 desktopmac/starskydesktop/starskydesktopUITests/starskydesktopUITests.swift delete mode 100644 desktopmac/starskydesktop/starskydesktopUITests/starskydesktopUITestsLaunchTests.swift diff --git a/desktopmac/starskydesktop/starskydesktop.xcodeproj/project.pbxproj b/desktopmac/starskydesktop/starskydesktop.xcodeproj/project.pbxproj deleted file mode 100644 index 0135a12576..0000000000 --- a/desktopmac/starskydesktop/starskydesktop.xcodeproj/project.pbxproj +++ /dev/null @@ -1,578 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 56; - objects = { - -/* Begin PBXBuildFile section */ - BCF3FD0A2B7A780B00EC31E2 /* starskydesktopApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCF3FD092B7A780B00EC31E2 /* starskydesktopApp.swift */; }; - BCF3FD0C2B7A780B00EC31E2 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCF3FD0B2B7A780B00EC31E2 /* ContentView.swift */; }; - BCF3FD0E2B7A780C00EC31E2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BCF3FD0D2B7A780C00EC31E2 /* Assets.xcassets */; }; - BCF3FD112B7A780C00EC31E2 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BCF3FD102B7A780C00EC31E2 /* Preview Assets.xcassets */; }; - BCF3FD1C2B7A780C00EC31E2 /* starskydesktopTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCF3FD1B2B7A780C00EC31E2 /* starskydesktopTests.swift */; }; - BCF3FD262B7A780C00EC31E2 /* starskydesktopUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCF3FD252B7A780C00EC31E2 /* starskydesktopUITests.swift */; }; - BCF3FD282B7A780C00EC31E2 /* starskydesktopUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCF3FD272B7A780C00EC31E2 /* starskydesktopUITestsLaunchTests.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - BCF3FD182B7A780C00EC31E2 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BCF3FCFE2B7A780B00EC31E2 /* Project object */; - proxyType = 1; - remoteGlobalIDString = BCF3FD052B7A780B00EC31E2; - remoteInfo = starskydesktop; - }; - BCF3FD222B7A780C00EC31E2 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BCF3FCFE2B7A780B00EC31E2 /* Project object */; - proxyType = 1; - remoteGlobalIDString = BCF3FD052B7A780B00EC31E2; - remoteInfo = starskydesktop; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - BCF3FD062B7A780B00EC31E2 /* starskydesktop.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = starskydesktop.app; sourceTree = BUILT_PRODUCTS_DIR; }; - BCF3FD092B7A780B00EC31E2 /* starskydesktopApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = starskydesktopApp.swift; sourceTree = ""; }; - BCF3FD0B2B7A780B00EC31E2 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - BCF3FD0D2B7A780C00EC31E2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - BCF3FD102B7A780C00EC31E2 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - BCF3FD122B7A780C00EC31E2 /* starskydesktop.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = starskydesktop.entitlements; sourceTree = ""; }; - BCF3FD172B7A780C00EC31E2 /* starskydesktopTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = starskydesktopTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - BCF3FD1B2B7A780C00EC31E2 /* starskydesktopTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = starskydesktopTests.swift; sourceTree = ""; }; - BCF3FD212B7A780C00EC31E2 /* starskydesktopUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = starskydesktopUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - BCF3FD252B7A780C00EC31E2 /* starskydesktopUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = starskydesktopUITests.swift; sourceTree = ""; }; - BCF3FD272B7A780C00EC31E2 /* starskydesktopUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = starskydesktopUITestsLaunchTests.swift; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - BCF3FD032B7A780B00EC31E2 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - BCF3FD142B7A780C00EC31E2 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - BCF3FD1E2B7A780C00EC31E2 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - BCF3FCFD2B7A780B00EC31E2 = { - isa = PBXGroup; - children = ( - BCF3FD082B7A780B00EC31E2 /* starskydesktop */, - BCF3FD1A2B7A780C00EC31E2 /* starskydesktopTests */, - BCF3FD242B7A780C00EC31E2 /* starskydesktopUITests */, - BCF3FD072B7A780B00EC31E2 /* Products */, - ); - sourceTree = ""; - }; - BCF3FD072B7A780B00EC31E2 /* Products */ = { - isa = PBXGroup; - children = ( - BCF3FD062B7A780B00EC31E2 /* starskydesktop.app */, - BCF3FD172B7A780C00EC31E2 /* starskydesktopTests.xctest */, - BCF3FD212B7A780C00EC31E2 /* starskydesktopUITests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - BCF3FD082B7A780B00EC31E2 /* starskydesktop */ = { - isa = PBXGroup; - children = ( - BCF3FD092B7A780B00EC31E2 /* starskydesktopApp.swift */, - BCF3FD0B2B7A780B00EC31E2 /* ContentView.swift */, - BCF3FD0D2B7A780C00EC31E2 /* Assets.xcassets */, - BCF3FD122B7A780C00EC31E2 /* starskydesktop.entitlements */, - BCF3FD0F2B7A780C00EC31E2 /* Preview Content */, - ); - path = starskydesktop; - sourceTree = ""; - }; - BCF3FD0F2B7A780C00EC31E2 /* Preview Content */ = { - isa = PBXGroup; - children = ( - BCF3FD102B7A780C00EC31E2 /* Preview Assets.xcassets */, - ); - path = "Preview Content"; - sourceTree = ""; - }; - BCF3FD1A2B7A780C00EC31E2 /* starskydesktopTests */ = { - isa = PBXGroup; - children = ( - BCF3FD1B2B7A780C00EC31E2 /* starskydesktopTests.swift */, - ); - path = starskydesktopTests; - sourceTree = ""; - }; - BCF3FD242B7A780C00EC31E2 /* starskydesktopUITests */ = { - isa = PBXGroup; - children = ( - BCF3FD252B7A780C00EC31E2 /* starskydesktopUITests.swift */, - BCF3FD272B7A780C00EC31E2 /* starskydesktopUITestsLaunchTests.swift */, - ); - path = starskydesktopUITests; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - BCF3FD052B7A780B00EC31E2 /* starskydesktop */ = { - isa = PBXNativeTarget; - buildConfigurationList = BCF3FD2B2B7A780C00EC31E2 /* Build configuration list for PBXNativeTarget "starskydesktop" */; - buildPhases = ( - BCF3FD022B7A780B00EC31E2 /* Sources */, - BCF3FD032B7A780B00EC31E2 /* Frameworks */, - BCF3FD042B7A780B00EC31E2 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = starskydesktop; - productName = starskydesktop; - productReference = BCF3FD062B7A780B00EC31E2 /* starskydesktop.app */; - productType = "com.apple.product-type.application"; - }; - BCF3FD162B7A780C00EC31E2 /* starskydesktopTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = BCF3FD2E2B7A780C00EC31E2 /* Build configuration list for PBXNativeTarget "starskydesktopTests" */; - buildPhases = ( - BCF3FD132B7A780C00EC31E2 /* Sources */, - BCF3FD142B7A780C00EC31E2 /* Frameworks */, - BCF3FD152B7A780C00EC31E2 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - BCF3FD192B7A780C00EC31E2 /* PBXTargetDependency */, - ); - name = starskydesktopTests; - productName = starskydesktopTests; - productReference = BCF3FD172B7A780C00EC31E2 /* starskydesktopTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - BCF3FD202B7A780C00EC31E2 /* starskydesktopUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = BCF3FD312B7A780C00EC31E2 /* Build configuration list for PBXNativeTarget "starskydesktopUITests" */; - buildPhases = ( - BCF3FD1D2B7A780C00EC31E2 /* Sources */, - BCF3FD1E2B7A780C00EC31E2 /* Frameworks */, - BCF3FD1F2B7A780C00EC31E2 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - BCF3FD232B7A780C00EC31E2 /* PBXTargetDependency */, - ); - name = starskydesktopUITests; - productName = starskydesktopUITests; - productReference = BCF3FD212B7A780C00EC31E2 /* starskydesktopUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - BCF3FCFE2B7A780B00EC31E2 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1520; - LastUpgradeCheck = 1520; - TargetAttributes = { - BCF3FD052B7A780B00EC31E2 = { - CreatedOnToolsVersion = 15.2; - }; - BCF3FD162B7A780C00EC31E2 = { - CreatedOnToolsVersion = 15.2; - TestTargetID = BCF3FD052B7A780B00EC31E2; - }; - BCF3FD202B7A780C00EC31E2 = { - CreatedOnToolsVersion = 15.2; - TestTargetID = BCF3FD052B7A780B00EC31E2; - }; - }; - }; - buildConfigurationList = BCF3FD012B7A780B00EC31E2 /* Build configuration list for PBXProject "starskydesktop" */; - compatibilityVersion = "Xcode 14.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = BCF3FCFD2B7A780B00EC31E2; - productRefGroup = BCF3FD072B7A780B00EC31E2 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - BCF3FD052B7A780B00EC31E2 /* starskydesktop */, - BCF3FD162B7A780C00EC31E2 /* starskydesktopTests */, - BCF3FD202B7A780C00EC31E2 /* starskydesktopUITests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - BCF3FD042B7A780B00EC31E2 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - BCF3FD112B7A780C00EC31E2 /* Preview Assets.xcassets in Resources */, - BCF3FD0E2B7A780C00EC31E2 /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - BCF3FD152B7A780C00EC31E2 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - BCF3FD1F2B7A780C00EC31E2 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - BCF3FD022B7A780B00EC31E2 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - BCF3FD0C2B7A780B00EC31E2 /* ContentView.swift in Sources */, - BCF3FD0A2B7A780B00EC31E2 /* starskydesktopApp.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - BCF3FD132B7A780C00EC31E2 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - BCF3FD1C2B7A780C00EC31E2 /* starskydesktopTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - BCF3FD1D2B7A780C00EC31E2 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - BCF3FD262B7A780C00EC31E2 /* starskydesktopUITests.swift in Sources */, - BCF3FD282B7A780C00EC31E2 /* starskydesktopUITestsLaunchTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - BCF3FD192B7A780C00EC31E2 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = BCF3FD052B7A780B00EC31E2 /* starskydesktop */; - targetProxy = BCF3FD182B7A780C00EC31E2 /* PBXContainerItemProxy */; - }; - BCF3FD232B7A780C00EC31E2 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = BCF3FD052B7A780B00EC31E2 /* starskydesktop */; - targetProxy = BCF3FD222B7A780C00EC31E2 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - BCF3FD292B7A780C00EC31E2 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - 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; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - 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; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 14.2; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - BCF3FD2A2B7A780C00EC31E2 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - 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; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - 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; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 14.2; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - }; - name = Release; - }; - BCF3FD2C2B7A780C00EC31E2 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = starskydesktop/starskydesktop.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = "\"starskydesktop/Preview Content\""; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = nl.qdraw.starskydesktop; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - BCF3FD2D2B7A780C00EC31E2 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = starskydesktop/starskydesktop.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = "\"starskydesktop/Preview Content\""; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = nl.qdraw.starskydesktop; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - BCF3FD2F2B7A780C00EC31E2 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 14.2; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = nl.qdraw.starskydesktopTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/starskydesktop.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/starskydesktop"; - }; - name = Debug; - }; - BCF3FD302B7A780C00EC31E2 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 14.2; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = nl.qdraw.starskydesktopTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/starskydesktop.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/starskydesktop"; - }; - name = Release; - }; - BCF3FD322B7A780C00EC31E2 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = nl.qdraw.starskydesktopUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TEST_TARGET_NAME = starskydesktop; - }; - name = Debug; - }; - BCF3FD332B7A780C00EC31E2 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = nl.qdraw.starskydesktopUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TEST_TARGET_NAME = starskydesktop; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - BCF3FD012B7A780B00EC31E2 /* Build configuration list for PBXProject "starskydesktop" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - BCF3FD292B7A780C00EC31E2 /* Debug */, - BCF3FD2A2B7A780C00EC31E2 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - BCF3FD2B2B7A780C00EC31E2 /* Build configuration list for PBXNativeTarget "starskydesktop" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - BCF3FD2C2B7A780C00EC31E2 /* Debug */, - BCF3FD2D2B7A780C00EC31E2 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - BCF3FD2E2B7A780C00EC31E2 /* Build configuration list for PBXNativeTarget "starskydesktopTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - BCF3FD2F2B7A780C00EC31E2 /* Debug */, - BCF3FD302B7A780C00EC31E2 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - BCF3FD312B7A780C00EC31E2 /* Build configuration list for PBXNativeTarget "starskydesktopUITests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - BCF3FD322B7A780C00EC31E2 /* Debug */, - BCF3FD332B7A780C00EC31E2 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = BCF3FCFE2B7A780B00EC31E2 /* Project object */; -} diff --git a/desktopmac/starskydesktop/starskydesktop.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/desktopmac/starskydesktop/starskydesktop.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a625..0000000000 --- a/desktopmac/starskydesktop/starskydesktop.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/desktopmac/starskydesktop/starskydesktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/desktopmac/starskydesktop/starskydesktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d..0000000000 --- a/desktopmac/starskydesktop/starskydesktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/desktopmac/starskydesktop/starskydesktop/Assets.xcassets/AccentColor.colorset/Contents.json b/desktopmac/starskydesktop/starskydesktop/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index eb87897008..0000000000 --- a/desktopmac/starskydesktop/starskydesktop/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "colors" : [ - { - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/desktopmac/starskydesktop/starskydesktop/Assets.xcassets/AppIcon.appiconset/Contents.json b/desktopmac/starskydesktop/starskydesktop/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 3f00db43ec..0000000000 --- a/desktopmac/starskydesktop/starskydesktop/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "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 - } -} diff --git a/desktopmac/starskydesktop/starskydesktop/Assets.xcassets/Contents.json b/desktopmac/starskydesktop/starskydesktop/Assets.xcassets/Contents.json deleted file mode 100644 index 73c00596a7..0000000000 --- a/desktopmac/starskydesktop/starskydesktop/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/desktopmac/starskydesktop/starskydesktop/ContentView.swift b/desktopmac/starskydesktop/starskydesktop/ContentView.swift deleted file mode 100644 index 30a3066e95..0000000000 --- a/desktopmac/starskydesktop/starskydesktop/ContentView.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// ContentView.swift -// starskydesktop -// -// Created by Dion on 12/02/2024. -// - -import SwiftUI - -struct ContentView: View { - var body: some View { - VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") - } - .padding() - } -} - -#Preview { - ContentView() -} diff --git a/desktopmac/starskydesktop/starskydesktop/Preview Content/Preview Assets.xcassets/Contents.json b/desktopmac/starskydesktop/starskydesktop/Preview Content/Preview Assets.xcassets/Contents.json deleted file mode 100644 index 73c00596a7..0000000000 --- a/desktopmac/starskydesktop/starskydesktop/Preview Content/Preview Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/desktopmac/starskydesktop/starskydesktop/starskydesktop.entitlements b/desktopmac/starskydesktop/starskydesktop/starskydesktop.entitlements deleted file mode 100644 index 18aff0ce43..0000000000 --- a/desktopmac/starskydesktop/starskydesktop/starskydesktop.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-only - - - diff --git a/desktopmac/starskydesktop/starskydesktop/starskydesktopApp.swift b/desktopmac/starskydesktop/starskydesktop/starskydesktopApp.swift deleted file mode 100644 index aa19d3eafd..0000000000 --- a/desktopmac/starskydesktop/starskydesktop/starskydesktopApp.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// starskydesktopApp.swift -// starskydesktop -// -// Created by Dion on 12/02/2024. -// - -import SwiftUI - -@main -struct starskydesktopApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } -} diff --git a/desktopmac/starskydesktop/starskydesktopTests/starskydesktopTests.swift b/desktopmac/starskydesktop/starskydesktopTests/starskydesktopTests.swift deleted file mode 100644 index f159dcb600..0000000000 --- a/desktopmac/starskydesktop/starskydesktopTests/starskydesktopTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// starskydesktopTests.swift -// starskydesktopTests -// -// Created by Dion on 12/02/2024. -// - -import XCTest -@testable import starskydesktop - -final class starskydesktopTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/desktopmac/starskydesktop/starskydesktopUITests/starskydesktopUITests.swift b/desktopmac/starskydesktop/starskydesktopUITests/starskydesktopUITests.swift deleted file mode 100644 index 56e1fc45dc..0000000000 --- a/desktopmac/starskydesktop/starskydesktopUITests/starskydesktopUITests.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// starskydesktopUITests.swift -// starskydesktopUITests -// -// Created by Dion on 12/02/2024. -// - -import XCTest - -final class starskydesktopUITests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - - // In UI tests it is usually best to stop immediately when a failure occurs. - continueAfterFailure = false - - // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // UI tests must launch the application that they test. - let app = XCUIApplication() - app.launch() - - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testLaunchPerformance() throws { - if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { - // This measures how long it takes to launch your application. - measure(metrics: [XCTApplicationLaunchMetric()]) { - XCUIApplication().launch() - } - } - } -} diff --git a/desktopmac/starskydesktop/starskydesktopUITests/starskydesktopUITestsLaunchTests.swift b/desktopmac/starskydesktop/starskydesktopUITests/starskydesktopUITestsLaunchTests.swift deleted file mode 100644 index 0e7bbad94a..0000000000 --- a/desktopmac/starskydesktop/starskydesktopUITests/starskydesktopUITestsLaunchTests.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// starskydesktopUITestsLaunchTests.swift -// starskydesktopUITests -// -// Created by Dion on 12/02/2024. -// - -import XCTest - -final class starskydesktopUITestsLaunchTests: XCTestCase { - - override class var runsForEachTargetApplicationUIConfiguration: Bool { - true - } - - override func setUpWithError() throws { - continueAfterFailure = false - } - - func testLaunch() throws { - let app = XCUIApplication() - app.launch() - - // Insert steps here to perform after app launch but before taking a screenshot, - // such as logging into a test account or navigating somewhere in the app - - let attachment = XCTAttachment(screenshot: app.screenshot()) - attachment.name = "Launch Screen" - attachment.lifetime = .keepAlways - add(attachment) - } -} From 036ef91ddf1cd54670d8277570ab81893ddac6ac Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 12 Feb 2024 22:58:58 +0100 Subject: [PATCH 006/125] add filter --- .../Helpers/MacOsOpenUrlTests.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs index fbe555ab4b..c8253f9f63 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs @@ -1,8 +1,10 @@ using System.Diagnostics; using System.Linq; +using System.Runtime.InteropServices; using System.Threading.Tasks; using Medallion.Shell; using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.native.Helpers; using starsky.foundation.native.OpenApplicationNative.Helpers; using starskytest.FakeCreateAn; @@ -15,8 +17,14 @@ public class MacOsOpenUrlTests private const string ConsoleName = "Console"; [TestMethod] - public async Task TestMethodWithSpecificApp() + public async Task TestMethodWithSpecificApp__MacOnly() { + if ( OperatingSystemHelper.GetPlatform() != OSPlatform.OSX ) + { + Assert.Inconclusive("This test if for Mac OS Only"); + return; + } + var filePath = new CreateAnImage().FullFilePath; MacOsOpenUrl.OpenApplicationAtUrl(filePath, ConsoleApp); @@ -38,8 +46,14 @@ public async Task TestMethodWithSpecificApp() } [TestMethod] - public void TestMethodWithDefaultApp() + public void TestMethodWithDefaultApp__MacOnly() { + if ( OperatingSystemHelper.GetPlatform() != OSPlatform.OSX ) + { + Assert.Inconclusive("This test if for Mac OS Only"); + return; + } + var result = MacOsOpenUrl.OpenDefault("urlNotFound"); Assert.IsFalse(result); } From dac7ad223844526a426d88f44818ea96d42f93bb Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 12 Feb 2024 23:00:17 +0100 Subject: [PATCH 007/125] add fixes --- .../Helpers/MacOsOpenUrl.cs | 28 ++++- .../Helpers/OtherPlatformsOpenDesktopApp.cs | 36 +++++++ .../IOpenApplicationNativeService.cs | 6 ++ .../OpenApplicationNativeService.cs | 22 +++- .../Trash/TrashService.cs | 13 +-- .../starsky.foundation.native.csproj | 7 +- .../Helpers/MacOsOpenUrlTests.cs | 101 ++++++++++-------- 7 files changed, 153 insertions(+), 60 deletions(-) create mode 100644 starsky/starsky.foundation.native/OpenApplicationNative/Helpers/OtherPlatformsOpenDesktopApp.cs create mode 100644 starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs index 0339fca47a..888698f804 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs @@ -5,6 +5,17 @@ namespace starsky.foundation.native.OpenApplicationNative.Helpers; public static class MacOsOpenUrl { + /// + /// Add check if not Mac OS X + /// + /// + /// + /// + internal static bool? OpenDefault( + string fileUrl, OSPlatform platform) + { + return platform != OSPlatform.OSX ? null : OpenDefault(fileUrl); + } /// /// Does NOT check if file exists @@ -14,8 +25,7 @@ public static class MacOsOpenUrl public static bool OpenDefault( string fileUrl) { - var fileUrlIntPtr = - MacOsTrashBindingHelper.GetUrls([fileUrl]).FirstOrDefault(); + var fileUrlIntPtr = MacOsTrashBindingHelper.GetUrls([fileUrl]).FirstOrDefault(); return objc_msgSend_retBool_IntPtr_IntPtr( NsWorkspaceSharedWorksPace(), @@ -23,12 +33,21 @@ public static bool OpenDefault( fileUrlIntPtr); } + internal static bool? OpenApplicationAtUrl( + string fileUrl, + string applicationUrl, OSPlatform platform) + { + return platform != OSPlatform.OSX ? null : OpenApplicationAtUrl(fileUrl, applicationUrl); + } + /// /// Does NOT check if a file exists + /// No Fallback if NOT Mac OS X /// /// Absolute Path /// Open with .app folder - public static void OpenApplicationAtUrl( + /// When not Mac OS + internal static bool? OpenApplicationAtUrl( string fileUrl, string applicationUrl) { @@ -51,6 +70,7 @@ public static void OpenApplicationAtUrl( applicationUrlIntPtr, nsWorkspaceOpenConfigurationDefault, IntPtr.Zero); + return true; } private const string FoundationFramework = @@ -74,7 +94,7 @@ private static extern IntPtr objc_msgSend_retVoid_IntPtr_IntPtr_IntPtr_IntPtr( [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] private static extern bool objc_msgSend_retBool_IntPtr_IntPtr(IntPtr target, IntPtr selector, IntPtr param); - + internal static IntPtr NsWorkspaceSharedWorksPace() { // Namespace diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/OtherPlatformsOpenDesktopApp.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/OtherPlatformsOpenDesktopApp.cs new file mode 100644 index 0000000000..8d2da1aaac --- /dev/null +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/OtherPlatformsOpenDesktopApp.cs @@ -0,0 +1,36 @@ +using System.Runtime.InteropServices; + +namespace starsky.foundation.native.OpenApplicationNative.Helpers; + +public static class OtherPlatformsOpenDesktopApp +{ + /// + /// Skip if is MacOS + /// + /// + /// + /// + /// + internal static bool? OpenApplicationAtUrl( + string fileUrl, + string applicationUrl, OSPlatform platform) + { + return platform == OSPlatform.OSX ? null : OpenApplicationAtUrl(fileUrl, applicationUrl); + } + + /// + /// Internal + /// + /// + /// + /// + internal static bool? OpenApplicationAtUrl( + string fileUrl, + string applicationUrl) + { + // do nothing + return null; + } + + +} diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs new file mode 100644 index 0000000000..e4b4dffe8f --- /dev/null +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs @@ -0,0 +1,6 @@ +namespace starsky.foundation.native.OpenApplicationNative.Interfaces; + +public interface IOpenApplicationNativeService +{ + bool? OpenApplicationAtUrl(string fullPath, string applicationUrl); +} diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs b/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs index 7ee433bf97..c6d544160c 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs @@ -1,6 +1,24 @@ +using starsky.foundation.injection; +using starsky.foundation.native.Helpers; +using starsky.foundation.native.OpenApplicationNative.Helpers; +using starsky.foundation.native.OpenApplicationNative.Interfaces; + namespace starsky.foundation.native.OpenApplicationNative; -public class OpenApplicationNativeService +[Service(typeof(IOpenApplicationNativeService), InjectionLifetime = InjectionLifetime.Scoped)] +public class OpenApplicationNativeService : IOpenApplicationNativeService { - + /// + /// + /// + /// system path + /// operation succeed (NOT if file is gone) + public bool? OpenApplicationAtUrl(string fullPath, string applicationUrl) + { + var currentPlatform = OperatingSystemHelper.GetPlatform(); + var macOsOpenResult = + MacOsOpenUrl.OpenApplicationAtUrl(fullPath, applicationUrl, currentPlatform); + var windowsOpenResult = false; + return macOsOpenResult ?? windowsOpenResult; + } } diff --git a/starsky/starsky.foundation.native/Trash/TrashService.cs b/starsky/starsky.foundation.native/Trash/TrashService.cs index 55a3734776..cc5b79d1ff 100644 --- a/starsky/starsky.foundation.native/Trash/TrashService.cs +++ b/starsky/starsky.foundation.native/Trash/TrashService.cs @@ -9,7 +9,6 @@ namespace starsky.foundation.native.Trash; [Service(typeof(ITrashService), InjectionLifetime = InjectionLifetime.Scoped)] public class TrashService : ITrashService { - /// /// Is the system trash supported /// @@ -33,14 +32,15 @@ public bool DetectToUseSystemTrash() /// Environment.UserInteractive /// Environment.UserName /// true if supported, false if not supported - internal static bool DetectToUseSystemTrashInternal(IsOsPlatformDelegate runtimeInformationIsOsPlatform, + internal static bool DetectToUseSystemTrashInternal( + IsOsPlatformDelegate runtimeInformationIsOsPlatform, bool environmentUserInteractive, string environmentUserName) { // ReSharper disable once ConvertIfStatementToReturnStatement if ( runtimeInformationIsOsPlatform(OSPlatform.Linux) || - runtimeInformationIsOsPlatform(OSPlatform.FreeBSD) || - environmentUserName == "root" || !environmentUserInteractive ) + runtimeInformationIsOsPlatform(OSPlatform.FreeBSD) || + environmentUserName == "root" || !environmentUserInteractive ) { return false; } @@ -63,10 +63,7 @@ internal static bool DetectToUseSystemTrashInternal(IsOsPlatformDelegate runtime /// operation succeed (NOT if file is gone) public bool? Trash(string fullPath) { - var currentPlatform = OperatingSystemHelper.GetPlatform(); - var macOsTrash = MacOsTrashBindingHelper.Trash(fullPath, currentPlatform); - var (windowsTrash, _) = WindowsShellTrashBindingHelper.Trash(fullPath, currentPlatform); - return macOsTrash ?? windowsTrash; + return Trash([fullPath]); } public bool? Trash(List fullPaths) diff --git a/starsky/starsky.foundation.native/starsky.foundation.native.csproj b/starsky/starsky.foundation.native/starsky.foundation.native.csproj index 7918c8d1ee..331e541374 100644 --- a/starsky/starsky.foundation.native/starsky.foundation.native.csproj +++ b/starsky/starsky.foundation.native/starsky.foundation.native.csproj @@ -12,7 +12,10 @@ - + + + + + - diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs index c8253f9f63..9cd2711aaf 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using Medallion.Shell; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -13,48 +14,60 @@ namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers; [TestClass] public class MacOsOpenUrlTests { - private const string ConsoleApp = "/System/Applications/Utilities/Console.app"; - private const string ConsoleName = "Console"; - - [TestMethod] - public async Task TestMethodWithSpecificApp__MacOnly() - { - if ( OperatingSystemHelper.GetPlatform() != OSPlatform.OSX ) - { - Assert.Inconclusive("This test if for Mac OS Only"); - return; - } - - var filePath = new CreateAnImage().FullFilePath; - - MacOsOpenUrl.OpenApplicationAtUrl(filePath, ConsoleApp); - - var isProcess = Process.GetProcessesByName(ConsoleName).Any(p => p.MainModule?.FileName.Contains(ConsoleApp) == true); - for (var i = 0; i < 15; i++) - { - isProcess = Process.GetProcessesByName(ConsoleName).Any(p => p.MainModule?.FileName.Contains(ConsoleApp) == true); - if (isProcess) - { - await Command.Run("osascript", "-e", "tell application \"Console\" to if it is running then quit").Task; - break; - } - - await Task.Delay(5); - } - - Assert.IsTrue(isProcess); - } - - [TestMethod] - public void TestMethodWithDefaultApp__MacOnly() - { - if ( OperatingSystemHelper.GetPlatform() != OSPlatform.OSX ) - { - Assert.Inconclusive("This test if for Mac OS Only"); - return; - } - - var result = MacOsOpenUrl.OpenDefault("urlNotFound"); - Assert.IsFalse(result); - } + // [TestMethod] + // public void TEst() + // { + // MacOsOpenUrl.OpenApplicationAtUrl( + // "/Users/dion/data/testcontent/20221029_101722_DSC05623.arw", + // "/Applications/Adobe Photoshop 2024/Adobe Photoshop 2024.app"); + // Thread.Sleep(50); + // } + + private const string ConsoleApp = "/System/Applications/Utilities/Console.app"; + private const string ConsoleName = "Console"; + + [TestMethod] + public async Task TestMethodWithSpecificApp__MacOnly() + { + if ( OperatingSystemHelper.GetPlatform() != OSPlatform.OSX ) + { + Assert.Inconclusive("This test if for Mac OS Only"); + return; + } + + var filePath = new CreateAnImage().FullFilePath; + + MacOsOpenUrl.OpenApplicationAtUrl(filePath, ConsoleApp); + + var isProcess = Process.GetProcessesByName(ConsoleName) + .Any(p => p.MainModule?.FileName.Contains(ConsoleApp) == true); + for ( var i = 0; i < 15; i++ ) + { + isProcess = Process.GetProcessesByName(ConsoleName) + .Any(p => p.MainModule?.FileName.Contains(ConsoleApp) == true); + if ( isProcess ) + { + await Command.Run("osascript", "-e", + "tell application \"Console\" to if it is running then quit").Task; + break; + } + + await Task.Delay(5); + } + + Assert.IsTrue(isProcess); + } + + [TestMethod] + public void TestMethodWithDefaultApp__MacOnly() + { + if ( OperatingSystemHelper.GetPlatform() != OSPlatform.OSX ) + { + Assert.Inconclusive("This test if for Mac OS Only"); + return; + } + + var result = MacOsOpenUrl.OpenDefault("urlNotFound"); + Assert.IsFalse(result); + } } From 816b94c6d84a1f27aaab39ff3eed093fa025f83c Mon Sep 17 00:00:00 2001 From: Dion Date: Tue, 13 Feb 2024 18:30:57 +0100 Subject: [PATCH 008/125] fix tests && add code for windows --- .../Helpers/MacOsOpenUrl.cs | 30 +++--- .../Helpers/OtherPlatformsOpenDesktopApp.cs | 36 ------- .../WindowsGetDefaultAppByExtension.cs | 94 +++++++++++++++++++ .../Helpers/WindowsOpenDesktopApp.cs | 85 +++++++++++++++++ .../IOpenApplicationNativeService.cs | 2 +- .../OpenApplicationNativeService.cs | 14 ++- .../Helpers => References}/OpenDefaultApp.bak | 0 .../Helpers/ExifTool.cs | 10 +- .../Helpers/MacOsOpenUrlTests.cs | 11 ++- 9 files changed, 218 insertions(+), 64 deletions(-) delete mode 100644 starsky/starsky.foundation.native/OpenApplicationNative/Helpers/OtherPlatformsOpenDesktopApp.cs create mode 100644 starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtension.cs create mode 100644 starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs rename starsky/starsky.foundation.native/{OpenApplicationNative/Helpers => References}/OpenDefaultApp.bak (100%) diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs index 888698f804..b5545c4df4 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs @@ -16,7 +16,7 @@ public static class MacOsOpenUrl { return platform != OSPlatform.OSX ? null : OpenDefault(fileUrl); } - + /// /// Does NOT check if file exists /// @@ -25,34 +25,40 @@ public static class MacOsOpenUrl public static bool OpenDefault( string fileUrl) { - var fileUrlIntPtr = MacOsTrashBindingHelper.GetUrls([fileUrl]).FirstOrDefault(); + var fileUrlsIntPtr = MacOsTrashBindingHelper.GetUrls([fileUrl]); - return objc_msgSend_retBool_IntPtr_IntPtr( - NsWorkspaceSharedWorksPace(), - MacOsTrashBindingHelper.GetSelector("openURL:"), - fileUrlIntPtr); + var result = new List(); + foreach ( var fileUrlIntPtr in fileUrlsIntPtr ) + { + result.Add(objc_msgSend_retBool_IntPtr_IntPtr( + NsWorkspaceSharedWorksPace(), + MacOsTrashBindingHelper.GetSelector("openURL:"), + fileUrlIntPtr)); + } + + return result.TrueForAll(p => p); } internal static bool? OpenApplicationAtUrl( - string fileUrl, + List fileUrls, string applicationUrl, OSPlatform platform) { - return platform != OSPlatform.OSX ? null : OpenApplicationAtUrl(fileUrl, applicationUrl); + return platform != OSPlatform.OSX ? null : OpenApplicationAtUrl(fileUrls, applicationUrl); } /// /// Does NOT check if a file exists /// No Fallback if NOT Mac OS X /// - /// Absolute Path + /// Absolute Paths /// Open with .app folder /// When not Mac OS internal static bool? OpenApplicationAtUrl( - string fileUrl, + List fileUrls, string applicationUrl) { - var fileUrlIntPtr = MacOsTrashBindingHelper.GetUrls([fileUrl]); - var fileUrlIntPtrUrlArray = MacOsTrashBindingHelper.CreateCfArray(fileUrlIntPtr); + var filesUrlIntPtr = MacOsTrashBindingHelper.GetUrls(fileUrls); + var fileUrlIntPtrUrlArray = MacOsTrashBindingHelper.CreateCfArray(filesUrlIntPtr); var applicationUrlIntPtr = MacOsTrashBindingHelper.GetUrls([applicationUrl]).FirstOrDefault(); diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/OtherPlatformsOpenDesktopApp.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/OtherPlatformsOpenDesktopApp.cs deleted file mode 100644 index 8d2da1aaac..0000000000 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/OtherPlatformsOpenDesktopApp.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Runtime.InteropServices; - -namespace starsky.foundation.native.OpenApplicationNative.Helpers; - -public static class OtherPlatformsOpenDesktopApp -{ - /// - /// Skip if is MacOS - /// - /// - /// - /// - /// - internal static bool? OpenApplicationAtUrl( - string fileUrl, - string applicationUrl, OSPlatform platform) - { - return platform == OSPlatform.OSX ? null : OpenApplicationAtUrl(fileUrl, applicationUrl); - } - - /// - /// Internal - /// - /// - /// - /// - internal static bool? OpenApplicationAtUrl( - string fileUrl, - string applicationUrl) - { - // do nothing - return null; - } - - -} diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtension.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtension.cs new file mode 100644 index 0000000000..f390b5ccb4 --- /dev/null +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtension.cs @@ -0,0 +1,94 @@ +using System.Runtime.InteropServices; +using System.Text; + +namespace starsky.foundation.native.OpenApplicationNative.Helpers; + +public class WindowsOpenDefaultApp +{ + + public void GetDefaultApp() + { + Console.WriteLine(AssocQueryString(AssocStr.Executable, ".png")); + } + + [DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)] + private static extern uint AssocQueryString( + AssocF flags, + AssocStr str, + string pszAssoc, + string pszExtra, + [Out] StringBuilder pszOut, + ref uint pcchOut + ); + + [Flags] + public enum AssocF + { + None = 0, + Init_NoRemapCLSID = 0x1, + Init_ByExeName = 0x2, + Open_ByExeName = 0x2, + Init_DefaultToStar = 0x4, + Init_DefaultToFolder = 0x8, + NoUserSettings = 0x10, + NoTruncate = 0x20, + Verify = 0x40, + RemapRunDll = 0x80, + NoFixUps = 0x100, + IgnoreBaseClass = 0x200, + Init_IgnoreUnknown = 0x400, + Init_Fixed_ProgId = 0x800, + Is_Protocol = 0x1000, + Init_For_File = 0x2000 + } + + public enum AssocStr + { + Command = 1, + Executable, + FriendlyDocName, + FriendlyAppName, + NoOpen, + ShellNewValue, + DDECommand, + DDEIfExec, + DDEApplication, + DDETopic, + InfoTip, + QuickTip, + TileInfo, + ContentType, + DefaultIcon, + ShellExtension, + DropTarget, + DelegateExecute, + Supported_Uri_Protocols, + ProgID, + AppID, + AppPublisher, + AppIconReference, + Ma + } + + private static string AssocQueryString(AssocStr association, string extension) + { + const int S_OK = 0; + const int S_FALSE = 1; + + uint length = 0; + uint ret = AssocQueryString(AssocF.None, association, extension, null, null, ref length); + if (ret != S_FALSE) + { + throw new InvalidOperationException("Could not determine associated string"); + } + + var sb = new StringBuilder((int)length); // (length-1) will probably work too as the marshaller adds null termination + ret = AssocQueryString(AssocF.None, association, extension, null, sb, ref length); + if (ret != S_OK) + { + throw new InvalidOperationException("Could not determine associated string"); + } + + return sb.ToString(); + } +} diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs new file mode 100644 index 0000000000..15af042826 --- /dev/null +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs @@ -0,0 +1,85 @@ +using System.Runtime.InteropServices; +using System.Text; +using static Medallion.Shell.Shell; + +namespace starsky.foundation.native.OpenApplicationNative.Helpers; + +public static class WindowsOpenDesktopApp +{ + + /// + /// Add check if not Mac OS X + /// + /// + /// + /// + internal static bool? OpenDefault( + string fileUrl, OSPlatform platform) + { + return platform != OSPlatform.OSX ? null : OpenDefault(fileUrl); + } + + /// + /// Does NOT check if file exists + /// + /// Absolute Path of file + /// + public static bool OpenDefault( + string fileUrl) + { + return false; + } + + /// + /// Skip if is MacOS + /// + /// + /// + /// + /// + internal static async Task OpenApplicationAtUrl( + List fileUrls, + string applicationUrl, OSPlatform platform) + { + return platform == OSPlatform.OSX + ? null + : await OpenApplicationAtUrl(fileUrls, applicationUrl); + } + + /// + /// Internal + /// + /// + /// + /// + internal static async Task OpenApplicationAtUrl( + List fileUrls, + string applicationUrl) + { + var command = Default.Run(applicationUrl, + options: + opts => + { + opts.StartInfo(si => + si.Arguments = GetArguments(fileUrls)); + }); + + var commandResult = await command.Task; + + return commandResult.Success; + } + + + internal static string GetArguments(List fileUrls) + { + // %windir%\system32\mspaint.exe C:\Users\mini\Desktop\travel.png + var arguments = new StringBuilder(); + foreach ( var url in fileUrls ) + { + arguments.Append($"\"{url}\" "); + } + + return arguments.ToString(); + } + +} diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs index e4b4dffe8f..040a8b5a2f 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs @@ -2,5 +2,5 @@ namespace starsky.foundation.native.OpenApplicationNative.Interfaces; public interface IOpenApplicationNativeService { - bool? OpenApplicationAtUrl(string fullPath, string applicationUrl); + Task OpenApplicationAtUrl(List fullPaths, string applicationUrl); } diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs b/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs index c6d544160c..aa96f1c93a 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs @@ -11,14 +11,18 @@ public class OpenApplicationNativeService : IOpenApplicationNativeService /// /// /// - /// system path + /// full path style + /// applicationUrl /// operation succeed (NOT if file is gone) - public bool? OpenApplicationAtUrl(string fullPath, string applicationUrl) + public async Task OpenApplicationAtUrl(List fullPaths, string applicationUrl) { var currentPlatform = OperatingSystemHelper.GetPlatform(); - var macOsOpenResult = - MacOsOpenUrl.OpenApplicationAtUrl(fullPath, applicationUrl, currentPlatform); - var windowsOpenResult = false; + var macOsOpenResult = MacOsOpenUrl.OpenApplicationAtUrl(fullPaths, + applicationUrl, currentPlatform); + + var windowsOpenResult = await WindowsOpenDesktopApp.OpenApplicationAtUrl(fullPaths, + applicationUrl, currentPlatform); + return macOsOpenResult ?? windowsOpenResult; } } diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/OpenDefaultApp.bak b/starsky/starsky.foundation.native/References/OpenDefaultApp.bak similarity index 100% rename from starsky/starsky.foundation.native/OpenApplicationNative/Helpers/OpenDefaultApp.bak rename to starsky/starsky.foundation.native/References/OpenDefaultApp.bak diff --git a/starsky/starsky.foundation.writemeta/Helpers/ExifTool.cs b/starsky/starsky.foundation.writemeta/Helpers/ExifTool.cs index 617f4a878f..21b966a224 100644 --- a/starsky/starsky.foundation.writemeta/Helpers/ExifTool.cs +++ b/starsky/starsky.foundation.writemeta/Helpers/ExifTool.cs @@ -74,7 +74,7 @@ private async Task> inputStream.Close(); if ( stream.Length <= 15 && ( await StreamToStringHelper.StreamToStringAsync(stream, true) ) - .Contains("Fake ExifTool", StringComparison.InvariantCultureIgnoreCase) ) + .Contains("Fake ExifTool", StringComparison.InvariantCultureIgnoreCase) ) { _logger.LogError( $"[WriteTagsAndRenameThumbnailAsync] Fake Exiftool detected {subPath}"); @@ -189,7 +189,7 @@ public async Task RunProcessAsync(string exifToolInputArguments) try { - // run with pipes + // run with pipes Command.Run( var command = Default.Run(_appSettings.ExifToolPath, options: opts => { opts.StartInfo(si => si.Arguments = argumentsWithPipeEnd); }); @@ -202,7 +202,7 @@ public async Task RunProcessAsync(string exifToolInputArguments) if ( _appSettings.IsVerbose() ) { _logger.LogInformation($"[RunProcessAsync] ~ exifTool {exifToolInputArguments} " + - $"run with result: {result.Success} ~ "); + $"run with result: {result.Success} ~ "); } memoryStream.Seek(0, SeekOrigin.Begin); @@ -212,8 +212,8 @@ public async Task RunProcessAsync(string exifToolInputArguments) catch ( Win32Exception exception ) { throw new ArgumentException("Error when trying to start the exifTool process. " + - "Please make sure exifTool is installed, and its path is properly " + - "specified in the options.", exception); + "Please make sure exifTool is installed, and its path is properly " + + "specified in the options.", exception); } } } diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs index 9cd2711aaf..6baaa12e4b 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; @@ -37,14 +38,14 @@ public async Task TestMethodWithSpecificApp__MacOnly() var filePath = new CreateAnImage().FullFilePath; - MacOsOpenUrl.OpenApplicationAtUrl(filePath, ConsoleApp); + MacOsOpenUrl.OpenApplicationAtUrl([filePath], ConsoleApp); - var isProcess = Process.GetProcessesByName(ConsoleName) - .Any(p => p.MainModule?.FileName.Contains(ConsoleApp) == true); + var isProcess = Process.GetProcessesByName(ConsoleName).ToList() + .Exists(p => p.MainModule?.FileName.Contains(ConsoleApp) == true); for ( var i = 0; i < 15; i++ ) { - isProcess = Process.GetProcessesByName(ConsoleName) - .Any(p => p.MainModule?.FileName.Contains(ConsoleApp) == true); + isProcess = Process.GetProcessesByName(ConsoleName).ToList() + .Exists(p => p.MainModule?.FileName.Contains(ConsoleApp) == true); if ( isProcess ) { await Command.Run("osascript", "-e", From b20eaaf72c2f81c1d382152d40f8aa003a9faccb Mon Sep 17 00:00:00 2001 From: Dion Date: Tue, 13 Feb 2024 19:25:36 +0100 Subject: [PATCH 009/125] fix issue --- .../WindowsGetDefaultAppByExtension.cs | 22 +++++++++---------- .../Helpers/MacOsOpenUrlTests.cs | 2 -- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtension.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtension.cs index f390b5ccb4..061d94a502 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtension.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtension.cs @@ -11,16 +11,6 @@ public void GetDefaultApp() Console.WriteLine(AssocQueryString(AssocStr.Executable, ".png")); } - [DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)] - private static extern uint AssocQueryString( - AssocF flags, - AssocStr str, - string pszAssoc, - string pszExtra, - [Out] StringBuilder pszOut, - ref uint pcchOut - ); - [Flags] public enum AssocF { @@ -70,13 +60,23 @@ public enum AssocStr Ma } + [DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)] + private static extern uint AssocQueryString( + AssocF flags, + AssocStr str, + string pszAssoc, + string? pszExtra, + [Out] StringBuilder? pszOut, + ref uint pcchOut + ); + private static string AssocQueryString(AssocStr association, string extension) { const int S_OK = 0; const int S_FALSE = 1; uint length = 0; - uint ret = AssocQueryString(AssocF.None, association, extension, null, null, ref length); + var ret = AssocQueryString(AssocF.None, association, extension, null, null, ref length); if (ret != S_FALSE) { throw new InvalidOperationException("Could not determine associated string"); diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs index 6baaa12e4b..12b711190c 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs @@ -1,8 +1,6 @@ -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; -using System.Threading; using System.Threading.Tasks; using Medallion.Shell; using Microsoft.VisualStudio.TestTools.UnitTesting; From c63e95ea75e590b62d64082da6cb156bb384e2b1 Mon Sep 17 00:00:00 2001 From: Dion Date: Tue, 13 Feb 2024 21:38:14 +0100 Subject: [PATCH 010/125] not working --- .../WindowsGetDefaultAppByExtension.cs | 397 +++++++++++++++--- .../WindowsGetDefaultAppByExtensionTests.cs | 22 + .../Helpers/WindowsOpenDesktopAppTests.cs | 20 + 3 files changed, 369 insertions(+), 70 deletions(-) create mode 100644 starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtensionTests.cs create mode 100644 starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtension.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtension.cs index 061d94a502..0e22b03ce0 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtension.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtension.cs @@ -1,94 +1,351 @@ +using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; namespace starsky.foundation.native.OpenApplicationNative.Helpers; -public class WindowsOpenDefaultApp +public static class WindowsGetDefaultAppByExtension { - public void GetDefaultApp() + + [DllImport("shlwapi.dll", CharSet = CharSet.Ansi)] + static extern int AssocQueryStringA(AssocF flags, ASSOCSTR str, string pszAssoc, string pszExtra, StringBuilder pszOut, ref uint pcchOut); + + //[Flags] + //public enum ASSOCF : uint + //{ + // INIT = 0x00000001, + // REMAPRUNDLL = 0x00000002, + // NOFIXUPS = 0x00000004, + // IGNOREBASECLASS = 0x00000008, + // INIT_IGNOREUNKNOWN = 0x00000010, + // INIT_FIXED_PROGID = 0x00000020, + // IS_PROTOCOL = 0x00000040, + // INIT_FOR_FILE = 0x00000080 + //} + + public enum ASSOCSTR { - Console.WriteLine(AssocQueryString(AssocStr.Executable, ".png")); + COMMAND = 1, + EXECUTABLE, + FRIENDLYDOCNAME, + FRIENDLYAPPNAME, + NOOPEN, + SHELLNEWVALUE, + DDECOMMAND, + DDEIFEXEC, + DDEAPPLICATION, + DDETOPIC, + INFOTIP, + QUICKTIP, + TILEINFO, + CONTENTTYPE, + DEFAULTICON, + SHELLEXTENSION, + DROPTARGET, + DELEGATEEXECUTE, + SUPPORTED_URI_PROTOCOLS, + PROGID, + APPID, + APPPUBLISHER, + APPICONREFERENCE, + MAX } - - [Flags] - public enum AssocF + + public static void GetDefaultApp3() { - None = 0, - Init_NoRemapCLSID = 0x1, - Init_ByExeName = 0x2, - Open_ByExeName = 0x2, - Init_DefaultToStar = 0x4, - Init_DefaultToFolder = 0x8, - NoUserSettings = 0x10, - NoTruncate = 0x20, - Verify = 0x40, - RemapRunDll = 0x80, - NoFixUps = 0x100, - IgnoreBaseClass = 0x200, - Init_IgnoreUnknown = 0x400, - Init_Fixed_ProgId = 0x800, - Is_Protocol = 0x1000, - Init_For_File = 0x2000 + string pszAssoc = ".png"; // Example file extension + string pszExtra = null; // Example extra parameter + + StringBuilder pszOut = new StringBuilder(1024); // Adjust buffer size accordingly + uint pcchOut = ( uint )pszOut.Capacity; + + int result = AssocQueryStringA(AssocF.ASSOCF_INIT_FOR_FILE, ASSOCSTR.EXECUTABLE, pszAssoc, pszExtra, pszOut, ref pcchOut); + + if ( result == 0 ) // S_OK + { + // Handle successful execution + Console.WriteLine("Associated executable: " + pszOut.ToString()); + } + else if ( result == 1 ) // S_FALSE + { + // Handle the case when pszOut is NULL, pcchOut contains the required buffer size + pszOut = new StringBuilder(( int )pcchOut); + result = AssocQueryStringA(AssocF.ASSOCF_INIT_FOR_FILE, ASSOCSTR.EXECUTABLE, pszAssoc, pszExtra, pszOut, ref pcchOut); + + if ( result == 0 ) + { + Console.WriteLine("Associated executable: " + pszOut.ToString()); + } + else + { + Console.WriteLine("Error: " + result); + } + } + else if ( result == unchecked(( int )0x80004003) ) // E_POINTER + { + // Handle the case when the buffer is too small + Console.WriteLine("Buffer too small."); + } + else + { + // Handle other errors + Console.WriteLine("Error: " + result); + } } - public enum AssocStr + + +[DllImport("shlwapi.dll", CharSet = CharSet.Ansi)] + static extern int AssocQueryKeyA(AssocF flags, ASSOCKEY key, string pszAssoc, string pszExtra, out IntPtr phkeyOut); + + //[Flags] + //public enum ASSOCF : uint + //{ + // INIT = 0x00000001, + // REMAPRUNDLL = 0x00000002, + // NOFIXUPS = 0x00000004, + // IGNOREBASECLASS = 0x00000008, + // INIT_IGNOREUNKNOWN = 0x00000010, + // INIT_FIXED_PROGID = 0x00000020, + // IS_PROTOCOL = 0x00000040, + // INIT_FOR_FILE = 0x00000080 + //} + + public enum ASSOCKEY { - Command = 1, - Executable, - FriendlyDocName, - FriendlyAppName, - NoOpen, - ShellNewValue, - DDECommand, - DDEIfExec, - DDEApplication, - DDETopic, - InfoTip, - QuickTip, - TileInfo, - ContentType, - DefaultIcon, - ShellExtension, - DropTarget, - DelegateExecute, - Supported_Uri_Protocols, - ProgID, - AppID, - AppPublisher, - AppIconReference, - Ma + SHELLNEW = 1, + SHELLEXECCLASS, + APP, + CLASS + } + + public static void GetDefaultApp2() + { + string pszAssoc = ".png"; // Example file extension + string pszExtra = null; // Example extra parameter + + IntPtr phkeyOut; + int result = AssocQueryKeyA(AssocF.ASSOCF_INIT_FOR_FILE, ASSOCKEY.CLASS, pszAssoc, pszExtra, out phkeyOut); + + if ( result == 0 ) // S_OK + { + // Handle successful execution + Console.WriteLine("Associated key successfully retrieved."); + } + else + { + // Handle error + Console.WriteLine("Error: " + result); + } } - - [DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)] - private static extern uint AssocQueryString( - AssocF flags, - AssocStr str, - string pszAssoc, - string? pszExtra, - [Out] StringBuilder? pszOut, - ref uint pcchOut - ); - - private static string AssocQueryString(AssocStr association, string extension) + + + +[DllImport("shell32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + static extern IntPtr FindExecutableA(string lpFile, string lpDirectory, StringBuilder lpResult); + + static string? FindApplication(string docName) { - const int S_OK = 0; - const int S_FALSE = 1; + const int MAX_PATH = 260; + StringBuilder result = new StringBuilder(MAX_PATH); + + IntPtr hInstance = FindExecutableA(docName, null, result); + + Console.WriteLine(hInstance.ToInt32()); - uint length = 0; - var ret = AssocQueryString(AssocF.None, association, extension, null, null, ref length); - if (ret != S_FALSE) + if ( hInstance.ToInt32() > 32 ) { - throw new InvalidOperationException("Could not determine associated string"); + return result.ToString(); } + else + { + return null; // Or any specific value to indicate failure + } + } + + public static void GetDefaultApp() + { + string documentName = "C:\\testcontent\\test.txt"; // Provide the path of the document here + string applicationPath = FindApplication(documentName); - var sb = new StringBuilder((int)length); // (length-1) will probably work too as the marshaller adds null termination - ret = AssocQueryString(AssocF.None, association, extension, null, sb, ref length); - if (ret != S_OK) + if ( applicationPath != null ) { - throw new InvalidOperationException("Could not determine associated string"); + Console.WriteLine("Associated application: " + applicationPath); } + else + { + Console.WriteLine("No associated application found."); + } + } + + - return sb.ToString(); + [DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Auto)] + static extern uint AssocQueryStringA(AssocF flags, AssocStr str, string pszAssoc, string pszExtra, [Out] StringBuilder pszOut, [In][Out] ref uint pcchOut); + /// + + /// The main entry point for the application. + /// + + [STAThread] + public static void GetDefaultApp1() + { + var extension = ".doc"; + Debug.WriteLine(FileExtentionInfo(AssocStr.ASSOCSTR_COMMAND, extension), "Command"); + Debug.WriteLine(FileExtentionInfo(AssocStr.ASSOCSTR_EXECUTABLE, extension), "Executable"); + //Debug.WriteLine(FileExtentionInfo(AssocStr.FriendlyAppName, extension), "FriendlyAppName"); + //Debug.WriteLine(FileExtentionInfo(AssocStr.FriendlyDocName, extension), "FriendlyDocName"); + //Debug.WriteLine(FileExtentionInfo(AssocStr.NoOpen, extension), "NoOpen"); + //Debug.WriteLine(FileExtentionInfo(AssocStr.ShellNewValue, extension), "ShellNewValue"); + // DDEApplication: WinWord + //DDEIfExec: Γ‘ο»΄ίΎ + // DDETopic: System + // Executable: C:\Program Files (x86)\Microsoft Office\Office12\WINWORD.EXE + // FriendlyAppName: Microsoft Office Word + // FriendlyDocName: Microsoft Office Word 97 - 2003 Document } + + public static string FileExtentionInfo(AssocStr assocStr, string doctype) + { + uint pcchOut = 0; + AssocQueryStringA(AssocF.ASSOCF_VERIFY, assocStr, doctype, null, null, ref pcchOut); + StringBuilder pszOut = new StringBuilder(( int )pcchOut); + AssocQueryStringA(AssocF.ASSOCF_VERIFY, assocStr, doctype, null, pszOut, ref pcchOut); + return pszOut.ToString(); + } + + //[Flags] + //public enum AssocF + //{ + // Init_NoRemapCLSID = 0x1, + // Init_ByExeName = 0x2, + // Open_ByExeName = 0x2, + // Init_DefaultToStar = 0x4, + // Init_DefaultToFolder = 0x8, + // NoUserSettings = 0x10, + // NoTruncate = 0x20, + // Verify = 0x40, + // RemapRunDll = 0x80, + // NoFixUps = 0x100, + // IgnoreBaseClass = 0x200 + //} + + //public enum AssocStr + //{ + // Command = 1, + // Executable, + // FriendlyDocName, + // FriendlyAppName, + // NoOpen, + // ShellNewValue, + // DDECommand, + // DDEIfExec, + // DDEApplication, + // DDETopic + //} + + + //public static void GetDefaultApp() + //{ + // Console.WriteLine(AssocQueryString(AssocStr.ASSOCSTR_EXECUTABLE, ".jpg")); + //} + + public enum AssocStr + { + ASSOCSTR_COMMAND = 1, + ASSOCSTR_EXECUTABLE, + ASSOCSTR_FRIENDLYDOCNAME, + ASSOCSTR_FRIENDLYAPPNAME, + ASSOCSTR_NOOPEN, + ASSOCSTR_SHELLNEWVALUE, + ASSOCSTR_DDECOMMAND, + ASSOCSTR_DDEIFEXEC, + ASSOCSTR_DDEAPPLICATION, + ASSOCSTR_DDETOPIC, + ASSOCSTR_INFOTIP, + ASSOCSTR_QUICKTIP, + ASSOCSTR_TILEINFO, + ASSOCSTR_CONTENTTYPE, + ASSOCSTR_DEFAULTICON, + ASSOCSTR_SHELLEXTENSION, + ASSOCSTR_DROPTARGET, + ASSOCSTR_DELEGATEEXECUTE, + ASSOCSTR_SUPPORTED_URI_PROTOCOLS, + ASSOCSTR_MAX + } + public enum AssocF + { + ASSOCF_NONE = 0x00000000, + ASSOCF_INIT_NOREMAPCLSID = 0x00000001, + ASSOCF_INIT_BYEXENAME = 0x00000002, + ASSOCF_OPEN_BYEXENAME = 0x00000002, + ASSOCF_INIT_DEFAULTTOSTAR = 0x00000004, + ASSOCF_INIT_DEFAULTTOFOLDER = 0x00000008, + ASSOCF_NOUSERSETTINGS = 0x00000010, + ASSOCF_NOTRUNCATE = 0x00000020, + ASSOCF_VERIFY = 0x00000040, + ASSOCF_REMAPRUNDLL = 0x00000080, + ASSOCF_NOFIXUPS = 0x00000100, + ASSOCF_IGNOREBASECLASS = 0x00000200, + ASSOCF_INIT_IGNOREUNKNOWN = 0x00000400, + ASSOCF_INIT_FIXED_PROGID = 0x00000800, + ASSOCF_IS_PROTOCOL = 0x00001000, + ASSOCF_INIT_FOR_FILE = 0x00002000 + } + + //[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)] + //private static extern uint AssocQueryString( + // AssocF flags, + // AssocStr str, + // string pszAssoc, + // string pszExtra, + // [Out] StringBuilder pszOut, + // ref uint pcchOut + //); + + + //static string AssocQueryString(AssocStr association, string extension) + //{ + // const int S_OK = 0; + // const int S_FALSE = 1; + + // uint length = 5; + // uint ret = AssocQueryString(AssocF.ASSOCF_NONE, association, extension, null, null, ref length); + // if ( ret != S_FALSE ) + // { + // throw new InvalidOperationException("Could not determine associated string"); + // } + + // var sb = new StringBuilder(( int )length); + // ret = AssocQueryString(AssocF.ASSOCF_NONE, association, extension, null, sb, ref length); + // if ( ret != S_OK ) + // { + // throw new InvalidOperationException("Could not determine associated string"); + // } + + // return sb.ToString(); + + //} + //private static string AssocQueryString(AssocStr association, string extension) + //{ + // const int S_OK = 0; + // const int S_FALSE = 1; + + // uint length = 0; + // var ret = AssocQueryString(AssocF.None, association, extension, null, null, ref length); + // if (ret != S_FALSE) + // { + // throw new InvalidOperationException("Could not determine associated string"); + // } + + // var sb = new StringBuilder((int)length); // (length-1) will probably work too as the marshaller adds null termination + // ret = AssocQueryString(AssocF.None, association, extension, null, sb, ref length); + // if (ret != S_OK) + // { + // throw new InvalidOperationException("Could not determine associated string"); + // } + + // return sb.ToString(); + //} } diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtensionTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtensionTests.cs new file mode 100644 index 0000000000..488e210ad5 --- /dev/null +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtensionTests.cs @@ -0,0 +1,22 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.native.OpenApplicationNative.Helpers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers +{ + [TestClass] + public class WindowsGetDefaultAppByExtensionTests + { + [TestMethod] + public void WindowsGetDefaultAppByExtension1() + { + WindowsGetDefaultAppByExtension.GetDefaultApp1(); + + WindowsGetDefaultAppByExtension.GetDefaultApp3(); + } + } +} diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs new file mode 100644 index 0000000000..fd91c898b1 --- /dev/null +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs @@ -0,0 +1,20 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.native.OpenApplicationNative.Helpers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers; + +[TestClass] +public class WindowsOpenDesktopAppTests +{ + [TestMethod] + public async Task OpenDefault() + { + WindowsOpenDesktopApp.OpenDefault("test"); + } +} + From 861a32bb1cad32f41c6ebacafd47d303780386b2 Mon Sep 17 00:00:00 2001 From: Dion Date: Tue, 13 Feb 2024 22:03:22 +0100 Subject: [PATCH 011/125] wip --- .../Helpers/WindowsOpenDesktopApp.cs | 13 +++++++++++-- .../Helpers/WindowsGetDefaultAppByExtensionTests.cs | 10 ++++++++++ .../Helpers/WindowsOpenDesktopAppTests.cs | 7 +++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs index 15af042826..ad503a84aa 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; using static Medallion.Shell.Shell; @@ -24,10 +25,18 @@ public static class WindowsOpenDesktopApp /// /// Absolute Path of file /// - public static bool OpenDefault( + public static bool? OpenDefault( string fileUrl) { - return false; + // var command = Default.Run(applicationUrl, + //options: + //opts => + //{ + // opts.StartInfo(si => + // si.Arguments = GetArguments(fileUrls)); + //}); + + return null; } /// diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtensionTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtensionTests.cs index 488e210ad5..11214c5f28 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtensionTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtensionTests.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.foundation.native.OpenApplicationNative.Helpers; +using starsky.foundation.storage.Services; using System; using System.Collections.Generic; using System.Linq; @@ -14,6 +15,15 @@ public class WindowsGetDefaultAppByExtensionTests [TestMethod] public void WindowsGetDefaultAppByExtension1() { + + //var filePath = "C:\\testcontent\\20221029_101722_DSC05623.jpg"; + //System.Diagnostics.Process proc = new System.Diagnostics.Process(); + //proc.EnableRaisingEvents = false; + //proc.StartInfo.FileName = "rundll32.exe"; + //proc.StartInfo.Arguments = "shell32,OpenAs_RunDLL " + filePath; + //proc.Start(); + + WindowsGetDefaultAppByExtension.GetDefaultApp1(); WindowsGetDefaultAppByExtension.GetDefaultApp3(); diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs index fd91c898b1..cf9606c6b7 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs @@ -2,6 +2,7 @@ using starsky.foundation.native.OpenApplicationNative.Helpers; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -15,6 +16,12 @@ public class WindowsOpenDesktopAppTests public async Task OpenDefault() { WindowsOpenDesktopApp.OpenDefault("test"); + + ProcessStartInfo psi = new ProcessStartInfo(); + psi.FileName = "C:\\testcontent\\20221029_101722_DSC05623.arw"; + psi.UseShellExecute = true; + psi.WindowStyle = ProcessWindowStyle.Normal; + Process.Start(psi); } } From f2e58e9381e92f0388c2c56b1a43d6a2ad1dabda Mon Sep 17 00:00:00 2001 From: Dion Date: Tue, 13 Feb 2024 22:09:15 +0100 Subject: [PATCH 012/125] WIP --- .../WindowsGetDefaultAppByExtension.cs | 351 ------------------ .../Helpers/WindowsOpenDesktopApp.cs | 55 +-- .../Helpers/WindowsSetFileAssociations.cs | 88 +++++ .../IOpenApplicationNativeService.cs | 2 +- .../OpenApplicationNativeService.cs | 4 +- .../starsky.foundation.native.csproj | 6 +- .../FakeCreateAn/CreateAnExifToolWindows.cs | 9 +- .../CreateFakeStarskyExe.cs | 40 ++ .../CreateFakeStarskyExe/starsky.starsky | 1 + .../Helpers/OpenDefaultAppTests.bak | 19 - .../WindowsGetDefaultAppByExtensionTests.cs | 32 -- .../Helpers/WindowsOpenDesktopAppTests.cs | 47 ++- .../WindowsSetFileAssociationsTests.cs | 30 ++ starsky/starskytest/starskytest.csproj | 108 +++--- 14 files changed, 295 insertions(+), 497 deletions(-) delete mode 100644 starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtension.cs create mode 100644 starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs create mode 100644 starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyExe.cs create mode 100644 starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/starsky.starsky delete mode 100644 starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/OpenDefaultAppTests.bak delete mode 100644 starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtensionTests.cs create mode 100644 starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtension.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtension.cs deleted file mode 100644 index 0e22b03ce0..0000000000 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtension.cs +++ /dev/null @@ -1,351 +0,0 @@ -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Text; - -namespace starsky.foundation.native.OpenApplicationNative.Helpers; - -public static class WindowsGetDefaultAppByExtension -{ - - - [DllImport("shlwapi.dll", CharSet = CharSet.Ansi)] - static extern int AssocQueryStringA(AssocF flags, ASSOCSTR str, string pszAssoc, string pszExtra, StringBuilder pszOut, ref uint pcchOut); - - //[Flags] - //public enum ASSOCF : uint - //{ - // INIT = 0x00000001, - // REMAPRUNDLL = 0x00000002, - // NOFIXUPS = 0x00000004, - // IGNOREBASECLASS = 0x00000008, - // INIT_IGNOREUNKNOWN = 0x00000010, - // INIT_FIXED_PROGID = 0x00000020, - // IS_PROTOCOL = 0x00000040, - // INIT_FOR_FILE = 0x00000080 - //} - - public enum ASSOCSTR - { - COMMAND = 1, - EXECUTABLE, - FRIENDLYDOCNAME, - FRIENDLYAPPNAME, - NOOPEN, - SHELLNEWVALUE, - DDECOMMAND, - DDEIFEXEC, - DDEAPPLICATION, - DDETOPIC, - INFOTIP, - QUICKTIP, - TILEINFO, - CONTENTTYPE, - DEFAULTICON, - SHELLEXTENSION, - DROPTARGET, - DELEGATEEXECUTE, - SUPPORTED_URI_PROTOCOLS, - PROGID, - APPID, - APPPUBLISHER, - APPICONREFERENCE, - MAX - } - - public static void GetDefaultApp3() - { - string pszAssoc = ".png"; // Example file extension - string pszExtra = null; // Example extra parameter - - StringBuilder pszOut = new StringBuilder(1024); // Adjust buffer size accordingly - uint pcchOut = ( uint )pszOut.Capacity; - - int result = AssocQueryStringA(AssocF.ASSOCF_INIT_FOR_FILE, ASSOCSTR.EXECUTABLE, pszAssoc, pszExtra, pszOut, ref pcchOut); - - if ( result == 0 ) // S_OK - { - // Handle successful execution - Console.WriteLine("Associated executable: " + pszOut.ToString()); - } - else if ( result == 1 ) // S_FALSE - { - // Handle the case when pszOut is NULL, pcchOut contains the required buffer size - pszOut = new StringBuilder(( int )pcchOut); - result = AssocQueryStringA(AssocF.ASSOCF_INIT_FOR_FILE, ASSOCSTR.EXECUTABLE, pszAssoc, pszExtra, pszOut, ref pcchOut); - - if ( result == 0 ) - { - Console.WriteLine("Associated executable: " + pszOut.ToString()); - } - else - { - Console.WriteLine("Error: " + result); - } - } - else if ( result == unchecked(( int )0x80004003) ) // E_POINTER - { - // Handle the case when the buffer is too small - Console.WriteLine("Buffer too small."); - } - else - { - // Handle other errors - Console.WriteLine("Error: " + result); - } - } - - - -[DllImport("shlwapi.dll", CharSet = CharSet.Ansi)] - static extern int AssocQueryKeyA(AssocF flags, ASSOCKEY key, string pszAssoc, string pszExtra, out IntPtr phkeyOut); - - //[Flags] - //public enum ASSOCF : uint - //{ - // INIT = 0x00000001, - // REMAPRUNDLL = 0x00000002, - // NOFIXUPS = 0x00000004, - // IGNOREBASECLASS = 0x00000008, - // INIT_IGNOREUNKNOWN = 0x00000010, - // INIT_FIXED_PROGID = 0x00000020, - // IS_PROTOCOL = 0x00000040, - // INIT_FOR_FILE = 0x00000080 - //} - - public enum ASSOCKEY - { - SHELLNEW = 1, - SHELLEXECCLASS, - APP, - CLASS - } - - public static void GetDefaultApp2() - { - string pszAssoc = ".png"; // Example file extension - string pszExtra = null; // Example extra parameter - - IntPtr phkeyOut; - int result = AssocQueryKeyA(AssocF.ASSOCF_INIT_FOR_FILE, ASSOCKEY.CLASS, pszAssoc, pszExtra, out phkeyOut); - - if ( result == 0 ) // S_OK - { - // Handle successful execution - Console.WriteLine("Associated key successfully retrieved."); - } - else - { - // Handle error - Console.WriteLine("Error: " + result); - } - } - - - -[DllImport("shell32.dll", CharSet = CharSet.Ansi, SetLastError = true)] - static extern IntPtr FindExecutableA(string lpFile, string lpDirectory, StringBuilder lpResult); - - static string? FindApplication(string docName) - { - const int MAX_PATH = 260; - StringBuilder result = new StringBuilder(MAX_PATH); - - IntPtr hInstance = FindExecutableA(docName, null, result); - - Console.WriteLine(hInstance.ToInt32()); - - if ( hInstance.ToInt32() > 32 ) - { - return result.ToString(); - } - else - { - return null; // Or any specific value to indicate failure - } - } - - public static void GetDefaultApp() - { - string documentName = "C:\\testcontent\\test.txt"; // Provide the path of the document here - string applicationPath = FindApplication(documentName); - - if ( applicationPath != null ) - { - Console.WriteLine("Associated application: " + applicationPath); - } - else - { - Console.WriteLine("No associated application found."); - } - } - - - - [DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Auto)] - static extern uint AssocQueryStringA(AssocF flags, AssocStr str, string pszAssoc, string pszExtra, [Out] StringBuilder pszOut, [In][Out] ref uint pcchOut); - /// - - /// The main entry point for the application. - /// - - [STAThread] - public static void GetDefaultApp1() - { - var extension = ".doc"; - Debug.WriteLine(FileExtentionInfo(AssocStr.ASSOCSTR_COMMAND, extension), "Command"); - Debug.WriteLine(FileExtentionInfo(AssocStr.ASSOCSTR_EXECUTABLE, extension), "Executable"); - //Debug.WriteLine(FileExtentionInfo(AssocStr.FriendlyAppName, extension), "FriendlyAppName"); - //Debug.WriteLine(FileExtentionInfo(AssocStr.FriendlyDocName, extension), "FriendlyDocName"); - //Debug.WriteLine(FileExtentionInfo(AssocStr.NoOpen, extension), "NoOpen"); - //Debug.WriteLine(FileExtentionInfo(AssocStr.ShellNewValue, extension), "ShellNewValue"); - // DDEApplication: WinWord - //DDEIfExec: Γ‘ο»΄ίΎ - // DDETopic: System - // Executable: C:\Program Files (x86)\Microsoft Office\Office12\WINWORD.EXE - // FriendlyAppName: Microsoft Office Word - // FriendlyDocName: Microsoft Office Word 97 - 2003 Document - } - - public static string FileExtentionInfo(AssocStr assocStr, string doctype) - { - uint pcchOut = 0; - AssocQueryStringA(AssocF.ASSOCF_VERIFY, assocStr, doctype, null, null, ref pcchOut); - StringBuilder pszOut = new StringBuilder(( int )pcchOut); - AssocQueryStringA(AssocF.ASSOCF_VERIFY, assocStr, doctype, null, pszOut, ref pcchOut); - return pszOut.ToString(); - } - - //[Flags] - //public enum AssocF - //{ - // Init_NoRemapCLSID = 0x1, - // Init_ByExeName = 0x2, - // Open_ByExeName = 0x2, - // Init_DefaultToStar = 0x4, - // Init_DefaultToFolder = 0x8, - // NoUserSettings = 0x10, - // NoTruncate = 0x20, - // Verify = 0x40, - // RemapRunDll = 0x80, - // NoFixUps = 0x100, - // IgnoreBaseClass = 0x200 - //} - - //public enum AssocStr - //{ - // Command = 1, - // Executable, - // FriendlyDocName, - // FriendlyAppName, - // NoOpen, - // ShellNewValue, - // DDECommand, - // DDEIfExec, - // DDEApplication, - // DDETopic - //} - - - //public static void GetDefaultApp() - //{ - // Console.WriteLine(AssocQueryString(AssocStr.ASSOCSTR_EXECUTABLE, ".jpg")); - //} - - public enum AssocStr - { - ASSOCSTR_COMMAND = 1, - ASSOCSTR_EXECUTABLE, - ASSOCSTR_FRIENDLYDOCNAME, - ASSOCSTR_FRIENDLYAPPNAME, - ASSOCSTR_NOOPEN, - ASSOCSTR_SHELLNEWVALUE, - ASSOCSTR_DDECOMMAND, - ASSOCSTR_DDEIFEXEC, - ASSOCSTR_DDEAPPLICATION, - ASSOCSTR_DDETOPIC, - ASSOCSTR_INFOTIP, - ASSOCSTR_QUICKTIP, - ASSOCSTR_TILEINFO, - ASSOCSTR_CONTENTTYPE, - ASSOCSTR_DEFAULTICON, - ASSOCSTR_SHELLEXTENSION, - ASSOCSTR_DROPTARGET, - ASSOCSTR_DELEGATEEXECUTE, - ASSOCSTR_SUPPORTED_URI_PROTOCOLS, - ASSOCSTR_MAX - } - public enum AssocF - { - ASSOCF_NONE = 0x00000000, - ASSOCF_INIT_NOREMAPCLSID = 0x00000001, - ASSOCF_INIT_BYEXENAME = 0x00000002, - ASSOCF_OPEN_BYEXENAME = 0x00000002, - ASSOCF_INIT_DEFAULTTOSTAR = 0x00000004, - ASSOCF_INIT_DEFAULTTOFOLDER = 0x00000008, - ASSOCF_NOUSERSETTINGS = 0x00000010, - ASSOCF_NOTRUNCATE = 0x00000020, - ASSOCF_VERIFY = 0x00000040, - ASSOCF_REMAPRUNDLL = 0x00000080, - ASSOCF_NOFIXUPS = 0x00000100, - ASSOCF_IGNOREBASECLASS = 0x00000200, - ASSOCF_INIT_IGNOREUNKNOWN = 0x00000400, - ASSOCF_INIT_FIXED_PROGID = 0x00000800, - ASSOCF_IS_PROTOCOL = 0x00001000, - ASSOCF_INIT_FOR_FILE = 0x00002000 - } - - //[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)] - //private static extern uint AssocQueryString( - // AssocF flags, - // AssocStr str, - // string pszAssoc, - // string pszExtra, - // [Out] StringBuilder pszOut, - // ref uint pcchOut - //); - - - //static string AssocQueryString(AssocStr association, string extension) - //{ - // const int S_OK = 0; - // const int S_FALSE = 1; - - // uint length = 5; - // uint ret = AssocQueryString(AssocF.ASSOCF_NONE, association, extension, null, null, ref length); - // if ( ret != S_FALSE ) - // { - // throw new InvalidOperationException("Could not determine associated string"); - // } - - // var sb = new StringBuilder(( int )length); - // ret = AssocQueryString(AssocF.ASSOCF_NONE, association, extension, null, sb, ref length); - // if ( ret != S_OK ) - // { - // throw new InvalidOperationException("Could not determine associated string"); - // } - - // return sb.ToString(); - - //} - //private static string AssocQueryString(AssocStr association, string extension) - //{ - // const int S_OK = 0; - // const int S_FALSE = 1; - - // uint length = 0; - // var ret = AssocQueryString(AssocF.None, association, extension, null, null, ref length); - // if (ret != S_FALSE) - // { - // throw new InvalidOperationException("Could not determine associated string"); - // } - - // var sb = new StringBuilder((int)length); // (length-1) will probably work too as the marshaller adds null termination - // ret = AssocQueryString(AssocF.None, association, extension, null, sb, ref length); - // if (ret != S_OK) - // { - // throw new InvalidOperationException("Could not determine associated string"); - // } - - // return sb.ToString(); - //} -} diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs index ad503a84aa..56d3a8ac8f 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs @@ -1,7 +1,7 @@ +using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; -using static Medallion.Shell.Shell; namespace starsky.foundation.native.OpenApplicationNative.Helpers; @@ -9,7 +9,7 @@ public static class WindowsOpenDesktopApp { /// - /// Add check if not Mac OS X + /// Add check if is Windows /// /// /// @@ -28,15 +28,19 @@ public static class WindowsOpenDesktopApp public static bool? OpenDefault( string fileUrl) { - // var command = Default.Run(applicationUrl, - //options: - //opts => - //{ - // opts.StartInfo(si => - // si.Arguments = GetArguments(fileUrls)); - //}); - - return null; + try + { + var projectStartInfo = new ProcessStartInfo(); + projectStartInfo.FileName = fileUrl; + projectStartInfo.UseShellExecute = true; + projectStartInfo.WindowStyle = ProcessWindowStyle.Normal; + var projectProcess = Process.Start(projectStartInfo); + return projectProcess != null; + } + catch ( Win32Exception ) + { + return false; + } } /// @@ -46,13 +50,13 @@ public static class WindowsOpenDesktopApp /// /// /// - internal static async Task OpenApplicationAtUrl( + internal static bool? OpenApplicationAtUrl( List fileUrls, string applicationUrl, OSPlatform platform) { return platform == OSPlatform.OSX ? null - : await OpenApplicationAtUrl(fileUrls, applicationUrl); + : OpenApplicationAtUrl(fileUrls, applicationUrl); } /// @@ -61,21 +65,26 @@ public static class WindowsOpenDesktopApp /// /// /// - internal static async Task OpenApplicationAtUrl( + internal static bool OpenApplicationAtUrl( List fileUrls, string applicationUrl) { - var command = Default.Run(applicationUrl, - options: - opts => - { - opts.StartInfo(si => - si.Arguments = GetArguments(fileUrls)); - }); + var projectStartInfo = new ProcessStartInfo(); + projectStartInfo.FileName = applicationUrl; + projectStartInfo.WindowStyle = ProcessWindowStyle.Normal; + + projectStartInfo.Arguments = GetArguments(fileUrls); + // not sure if needed + projectStartInfo.LoadUserProfile = true; - var commandResult = await command.Task; + var process = new Process + { + StartInfo = projectStartInfo + }; + process.Start(); - return commandResult.Success; + process.Dispose(); + return true; } diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs new file mode 100644 index 0000000000..77638b912a --- /dev/null +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs @@ -0,0 +1,88 @@ +using System.Diagnostics; +using Microsoft.Win32; + +namespace starsky.foundation.native.OpenApplicationNative.Helpers; + +public class FileAssociation +{ + public string Extension { get; set; } = string.Empty; + public string ProgId { get; set; } = string.Empty; + public string FileTypeDescription { get; set; } = string.Empty; + public string ExecutableFilePath { get; set; } = string.Empty; +} + + +public static class WindowsSetFileAssociations +{ + /// + /// needed so that Explorer windows get refreshed after the registry is updated + /// https://stackoverflow.com/questions/2681878/associate-file-extension-with-application + /// + /// + /// + /// + /// + /// + [System.Runtime.InteropServices.DllImport("Shell32.dll")] + private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2); + + private const int SHCNE_ASSOCCHANGED = 0x8000000; + private const int SHCNF_FLUSH = 0x1000; + + public static void EnsureAssociationsSet() + { + var filePath = Process.GetCurrentProcess().MainModule.FileName; + EnsureAssociationsSet( + new FileAssociation + { + Extension = ".ucs", + ProgId = "UCS_Editor_File", + FileTypeDescription = "UCS File", + ExecutableFilePath = filePath + }); + } + + public static void EnsureAssociationsSet(params FileAssociation[] associations) + { + bool madeChanges = false; + foreach ( var association in associations ) + { + madeChanges |= SetAssociation( + association.Extension, + association.ProgId, + association.FileTypeDescription, + association.ExecutableFilePath); + } + + if ( madeChanges ) + { + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero); + } + } + + public static bool SetAssociation(string extension, string progId, string fileTypeDescription, + string applicationFilePath) + { + bool madeChanges = false; + madeChanges |= SetKeyDefaultValue(@"Software\Classes\" + extension, progId); + madeChanges |= SetKeyDefaultValue(@"Software\Classes\" + progId, fileTypeDescription); + madeChanges |= SetKeyDefaultValue($@"Software\Classes\{progId}\shell\open\command", + "\"" + applicationFilePath + "\" \"%1\""); + return madeChanges; + } + + private static bool SetKeyDefaultValue(string keyPath, string value) + { + using ( var key = Registry.CurrentUser.CreateSubKey(keyPath) ) + { + if ( key.GetValue(null) as string != value ) + { + key.SetValue(null, value); + return true; + } + } + + return false; + } +} + diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs index 040a8b5a2f..c795570863 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs @@ -2,5 +2,5 @@ namespace starsky.foundation.native.OpenApplicationNative.Interfaces; public interface IOpenApplicationNativeService { - Task OpenApplicationAtUrl(List fullPaths, string applicationUrl); + bool? OpenApplicationAtUrl(List fullPaths, string applicationUrl); } diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs b/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs index aa96f1c93a..2740cd8b00 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs @@ -14,13 +14,13 @@ public class OpenApplicationNativeService : IOpenApplicationNativeService /// full path style /// applicationUrl /// operation succeed (NOT if file is gone) - public async Task OpenApplicationAtUrl(List fullPaths, string applicationUrl) + public bool? OpenApplicationAtUrl(List fullPaths, string applicationUrl) { var currentPlatform = OperatingSystemHelper.GetPlatform(); var macOsOpenResult = MacOsOpenUrl.OpenApplicationAtUrl(fullPaths, applicationUrl, currentPlatform); - var windowsOpenResult = await WindowsOpenDesktopApp.OpenApplicationAtUrl(fullPaths, + var windowsOpenResult = WindowsOpenDesktopApp.OpenApplicationAtUrl(fullPaths, applicationUrl, currentPlatform); return macOsOpenResult ?? windowsOpenResult; diff --git a/starsky/starsky.foundation.native/starsky.foundation.native.csproj b/starsky/starsky.foundation.native/starsky.foundation.native.csproj index 331e541374..957ee3721b 100644 --- a/starsky/starsky.foundation.native/starsky.foundation.native.csproj +++ b/starsky/starsky.foundation.native/starsky.foundation.native.csproj @@ -12,10 +12,6 @@ - - - - - + diff --git a/starsky/starskytest/FakeCreateAn/CreateAnExifToolWindows.cs b/starsky/starskytest/FakeCreateAn/CreateAnExifToolWindows.cs index b78869d55a..16db130987 100644 --- a/starsky/starskytest/FakeCreateAn/CreateAnExifToolWindows.cs +++ b/starsky/starskytest/FakeCreateAn/CreateAnExifToolWindows.cs @@ -287,9 +287,16 @@ public static class CreateAnExifToolWindows "ABAAJAAAAAAAAAAggKSBAAAAAGV4aWZ0b29sKC1rKS5leGUKACAAAAAAAAEAGAAAzW9bwS3WAQA0dNHB" + "LdYBAHu8iMEt1gFQSwUGAAAAAAEAAQBiAAAADUEAAAAA"; + /// + /// This is a zip file + /// public static readonly ImmutableArray Bytes = - Base64Helper.TryParse(ImageExifToolZipWindows).ToImmutableArray(); + [.. Base64Helper.TryParse(ImageExifToolZipWindows)]; + + /// + /// File hash to check the content of the zip file + /// public static readonly string Sha1 = "0da554d4cf5f4c15591da109ae070742ecfceb65"; } } diff --git a/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyExe.cs b/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyExe.cs new file mode 100644 index 0000000000..971c936349 --- /dev/null +++ b/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyExe.cs @@ -0,0 +1,40 @@ +using System.Collections.Immutable; +using System.IO; +using System.Reflection; +using starsky.foundation.storage.Storage; +using starskytest.FakeMocks; + +namespace starskytest.FakeCreateAn.CreateFakeStarskyExe +{ + public class CreateFakeStarskyExe + { + public CreateFakeStarskyExe() + { + var dirName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + if ( string.IsNullOrEmpty(dirName) ) return; + var parentFolder = Path.Combine(dirName, "FakeCreateAn", + "CreateFakeStarskyExe"); + var path = Path.Combine(parentFolder, "starsky.exe"); + FullFilePath = path; + StarskyDotStarskyPath = Path.Combine(parentFolder, "starsky.starsky"); + + Bytes = [.. StreamToBytes(path)]; + } + + public string FullFilePath { get; set; } = string.Empty; + public string StarskyDotStarskyPath { get; set; } = string.Empty; + + private static byte[] StreamToBytes(string path) + { + var input = new StorageHostFullPathFilesystem(new FakeIWebLogger()).ReadStream(path); + using var ms = new MemoryStream(); + input.CopyTo(ms); + input.Dispose(); + return ms.ToArray(); + } + + public readonly ImmutableArray Bytes = []; + + } +} + diff --git a/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/starsky.starsky b/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/starsky.starsky new file mode 100644 index 0000000000..1cddf9dbab --- /dev/null +++ b/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/starsky.starsky @@ -0,0 +1 @@ +# no content \ No newline at end of file diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/OpenDefaultAppTests.bak b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/OpenDefaultAppTests.bak deleted file mode 100644 index fde2387ff9..0000000000 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/OpenDefaultAppTests.bak +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Threading; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using starsky.foundation.native.OpenApplicationNative.Helpers; - -namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers; - -[TestClass] -public class OpenDefaultAppTests -{ - private const string ConsoleApp = "/System/Applications/Utilities/Console.app"; - - [TestMethod] - public void TEst() - { - MacOsOpenDefaultApp.SetDefaultApplicationAtURL(ConsoleApp,"/Users/dion/Desktop/mariadb.yml"); - Thread.Sleep(1000); - } -} diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtensionTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtensionTests.cs deleted file mode 100644 index 11214c5f28..0000000000 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsGetDefaultAppByExtensionTests.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using starsky.foundation.native.OpenApplicationNative.Helpers; -using starsky.foundation.storage.Services; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers -{ - [TestClass] - public class WindowsGetDefaultAppByExtensionTests - { - [TestMethod] - public void WindowsGetDefaultAppByExtension1() - { - - //var filePath = "C:\\testcontent\\20221029_101722_DSC05623.jpg"; - //System.Diagnostics.Process proc = new System.Diagnostics.Process(); - //proc.EnableRaisingEvents = false; - //proc.StartInfo.FileName = "rundll32.exe"; - //proc.StartInfo.Arguments = "shell32,OpenAs_RunDLL " + filePath; - //proc.Start(); - - - WindowsGetDefaultAppByExtension.GetDefaultApp1(); - - WindowsGetDefaultAppByExtension.GetDefaultApp3(); - } - } -} diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs index cf9606c6b7..900b3fe6d9 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs @@ -1,11 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.foundation.native.OpenApplicationNative.Helpers; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using starsky.foundation.platform.Models; +using starskytest.FakeCreateAn.CreateFakeStarskyExe; namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers; @@ -13,15 +9,40 @@ namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers; public class WindowsOpenDesktopAppTests { [TestMethod] - public async Task OpenDefault() + public void OpenDefault_HappyFlow() { - WindowsOpenDesktopApp.OpenDefault("test"); + if ( !new AppSettings().IsWindows ) + { + Assert.Inconclusive("This test if for Windows Only"); + return; + } - ProcessStartInfo psi = new ProcessStartInfo(); - psi.FileName = "C:\\testcontent\\20221029_101722_DSC05623.arw"; - psi.UseShellExecute = true; - psi.WindowStyle = ProcessWindowStyle.Normal; - Process.Start(psi); + var mock = new CreateFakeStarskyExe(); + var filePath = mock.FullFilePath; + WindowsSetFileAssociations.EnsureAssociationsSet( + new FileAssociation + { + Extension = ".starsky", + ProgId = "starskytest", + FileTypeDescription = "Starsky Test File", + ExecutableFilePath = filePath + }); + + var result = WindowsOpenDesktopApp.OpenDefault(mock.StarskyDotStarskyPath); + Assert.IsTrue(result); + } + + [TestMethod] + public void OpenDefault_FileNotFound() + { + if ( !new AppSettings().IsWindows ) + { + Assert.Inconclusive("This test if for Windows Only"); + return; + } + + var result = WindowsOpenDesktopApp.OpenDefault("not-found"); + Assert.IsFalse(result); } } diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs new file mode 100644 index 0000000000..a5a4c26a66 --- /dev/null +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs @@ -0,0 +1,30 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.native.OpenApplicationNative.Helpers; +using starsky.foundation.storage.Services; +using starskytest.FakeCreateAn.CreateFakeStarskyExe; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers +{ + [TestClass] + public class WindowsSetFileAssociationsTests + { + [TestMethod] + public void EnsureAssociationsSet() + { + var filePath = new CreateFakeStarskyExe().FullFilePath; + WindowsSetFileAssociations.EnsureAssociationsSet( + new FileAssociation + { + Extension = ".starsky", + ProgId = "starskytest", + FileTypeDescription = "Starsky Test File", + ExecutableFilePath = filePath + }); + } + } +} diff --git a/starsky/starskytest/starskytest.csproj b/starsky/starskytest/starskytest.csproj index 5953ee1eeb..442d4e6cd4 100644 --- a/starsky/starskytest/starskytest.csproj +++ b/starsky/starskytest/starskytest.csproj @@ -17,8 +17,8 @@ $(RootFolder)\build.vstest.runsettings - - + + @@ -34,85 +34,93 @@ - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + - - - + + + - - - - - - - + + + + + + + + + PreserveNewest - + - + build.vstest.runsettings - + PreserveNewest - - + + PreserveNewest + + PreserveNewest + + + PreserveNewest + From 8982082000027c234915d591f955b4e0f80695a1 Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 14 Feb 2024 18:18:53 +0100 Subject: [PATCH 013/125] add tests --- starsky/build.vstest.runsettings | 1 + .../Helpers/MacOsOpenUrl.cs | 58 +++++++---- .../Helpers/WindowsOpenDesktopApp.cs | 78 ++++++++------ .../Helpers/WindowsSetFileAssociations.cs | 19 +--- .../OpenApplicationNativeService.cs | 16 +++ .../CreateOpenApplicationNative/MacOsStub.cpp | 41 ++++++++ .../CreateOpenApplicationNative/MacOsStub.dll | Bin 0 -> 80896 bytes .../CreateOpenApplicationNative/MacOsStub.h | 23 +++++ .../CreateOpenApplicationNative/readme.md | 25 +++++ .../Helpers/OperatingSystemHelperTest.cs | 4 +- .../Helpers/MacOsOpenUrlTests.cs | 95 ++++++++++++++++-- .../Helpers/WindowsOpenDesktopAppTests.cs | 88 ++++++++++++++-- .../WindowsSetFileAssociationsTests.cs | 6 -- .../OpenApplicationNativeServiceTest.cs | 88 ++++++++++++++++ .../Helpers/PathHelperTest.cs | 2 +- 15 files changed, 448 insertions(+), 96 deletions(-) create mode 100644 starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/MacOsStub.cpp create mode 100644 starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/MacOsStub.dll create mode 100644 starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/MacOsStub.h create mode 100644 starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/readme.md create mode 100644 starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs diff --git a/starsky/build.vstest.runsettings b/starsky/build.vstest.runsettings index 444a80336f..5cd894b703 100644 --- a/starsky/build.vstest.runsettings +++ b/starsky/build.vstest.runsettings @@ -15,6 +15,7 @@ opencover [MySqlConnector]*,[starsky.Views]*,[*]starsky.foundation.database.Migrations.* + **/*.Generator.RegexGenerator/**,**/Migrations/** diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs index b5545c4df4..28d23cbaa9 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs @@ -8,37 +8,42 @@ public static class MacOsOpenUrl /// /// Add check if not Mac OS X /// - /// + /// /// /// internal static bool? OpenDefault( - string fileUrl, OSPlatform platform) + List fileUrls, OSPlatform platform) { - return platform != OSPlatform.OSX ? null : OpenDefault(fileUrl); + return platform != OSPlatform.OSX ? null : OpenDefault(fileUrls); } /// /// Does NOT check if file exists /// - /// Absolute Path of file + /// Absolute Path of file /// public static bool OpenDefault( - string fileUrl) + List fileUrls) { - var fileUrlsIntPtr = MacOsTrashBindingHelper.GetUrls([fileUrl]); + var fileUrlsIntPtr = MacOsTrashBindingHelper.GetUrls(fileUrls); var result = new List(); foreach ( var fileUrlIntPtr in fileUrlsIntPtr ) { - result.Add(objc_msgSend_retBool_IntPtr_IntPtr( - NsWorkspaceSharedWorksPace(), - MacOsTrashBindingHelper.GetSelector("openURL:"), - fileUrlIntPtr)); + result.Add(InvokeOpenUrl(fileUrlIntPtr)); } return result.TrueForAll(p => p); } + internal static bool InvokeOpenUrl(IntPtr fileUrlIntPtr) + { + return objc_msgSend_retBool_IntPtr_IntPtr( + NsWorkspaceSharedWorkSpace(), + MacOsTrashBindingHelper.GetSelector("openURL:"), + fileUrlIntPtr); + } + internal static bool? OpenApplicationAtUrl( List fileUrls, string applicationUrl, OSPlatform platform) @@ -68,20 +73,33 @@ public static bool OpenDefault( nsWorkspaceOpenConfiguration, MacOsTrashBindingHelper.GetSelector("configuration")); // https://developer.apple.com/documentation/appkit/nsworkspace/3172702-openurls?language=objc - objc_msgSend_retVoid_IntPtr_IntPtr_IntPtr_IntPtr( - NsWorkspaceSharedWorksPace(), - MacOsTrashBindingHelper.GetSelector( - "openURLs:withApplicationAtURL:configuration:completionHandler:"), - fileUrlIntPtrUrlArray, - applicationUrlIntPtr, - nsWorkspaceOpenConfigurationDefault, - IntPtr.Zero); + OpenURLsWithApplicationAtURL(fileUrlIntPtrUrlArray, applicationUrlIntPtr, nsWorkspaceOpenConfigurationDefault); return true; } + + +/// +/// @see: https://developer.apple.com/documentation/appkit/nsworkspace/3172702-openurls?language=objc +/// +internal static void OpenURLsWithApplicationAtURL(nint fileUrlIntPtrUrlArray, nint applicationUrlIntPtr, nint nsWorkspaceOpenConfigurationDefault) +{ + objc_msgSend_retVoid_IntPtr_IntPtr_IntPtr_IntPtr( + NsWorkspaceSharedWorkSpace(), + MacOsTrashBindingHelper.GetSelector( + "openURLs:withApplicationAtURL:configuration:completionHandler:"), + fileUrlIntPtrUrlArray, + applicationUrlIntPtr, + nsWorkspaceOpenConfigurationDefault, + IntPtr.Zero); + } + private const string FoundationFramework = "/System/Library/Frameworks/Foundation.framework/Foundation"; + private const string AppKitFramework = + "/System/Library/Frameworks/AppKit.framework/AppKit"; + [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] private static extern IntPtr objc_msgSend_retIntPtr(IntPtr target, IntPtr selector); @@ -94,14 +112,14 @@ private static extern IntPtr objc_msgSend_retVoid_IntPtr_IntPtr_IntPtr_IntPtr( IntPtr param3, IntPtr param4); - [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] + [DllImport(AppKitFramework)] static extern IntPtr objc_getClass(string className); [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] private static extern bool objc_msgSend_retBool_IntPtr_IntPtr(IntPtr target, IntPtr selector, IntPtr param); - internal static IntPtr NsWorkspaceSharedWorksPace() + internal static IntPtr NsWorkspaceSharedWorkSpace() { // Namespace var nsWorkspace = objc_getClass("NSWorkspace"); diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs index 56d3a8ac8f..c2f13f4c6e 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs @@ -11,20 +11,37 @@ public static class WindowsOpenDesktopApp /// /// Add check if is Windows /// - /// + /// /// /// internal static bool? OpenDefault( - string fileUrl, OSPlatform platform) + List fileUrls, OSPlatform platform) { - return platform != OSPlatform.OSX ? null : OpenDefault(fileUrl); + return platform != OSPlatform.Windows ? null : OpenDefault(fileUrls); } - /// - /// Does NOT check if file exists - /// - /// Absolute Path of file - /// + public static bool? OpenDefault( + List fileUrls) + { + if ( fileUrls.Count == 0 ) + { + return false; + } + + var result = new List(); + foreach ( var fileUrl in fileUrls ) + { + result.Add(OpenDefault(fileUrl)); + } + + return result.TrueForAll(p => p == true); + } + + /// + /// Does NOT check if file exists + /// + /// Absolute Path of file + /// public static bool? OpenDefault( string fileUrl) { @@ -54,7 +71,7 @@ public static class WindowsOpenDesktopApp List fileUrls, string applicationUrl, OSPlatform platform) { - return platform == OSPlatform.OSX + return platform != OSPlatform.Windows ? null : OpenApplicationAtUrl(fileUrls, applicationUrl); } @@ -69,35 +86,30 @@ internal static bool OpenApplicationAtUrl( List fileUrls, string applicationUrl) { - var projectStartInfo = new ProcessStartInfo(); - projectStartInfo.FileName = applicationUrl; - projectStartInfo.WindowStyle = ProcessWindowStyle.Normal; - - projectStartInfo.Arguments = GetArguments(fileUrls); - // not sure if needed - projectStartInfo.LoadUserProfile = true; - - var process = new Process + if ( fileUrls.Count == 0 ) { - StartInfo = projectStartInfo - }; - process.Start(); + return false; + } - process.Dispose(); - return true; - } - - - internal static string GetArguments(List fileUrls) - { - // %windir%\system32\mspaint.exe C:\Users\mini\Desktop\travel.png - var arguments = new StringBuilder(); + var results = new List(); foreach ( var url in fileUrls ) { - arguments.Append($"\"{url}\" "); + var projectStartInfo = new ProcessStartInfo(); + projectStartInfo.FileName = applicationUrl; + projectStartInfo.WindowStyle = ProcessWindowStyle.Normal; + projectStartInfo.Arguments = url; + + var process = new Process + { + StartInfo = projectStartInfo + }; + var projectProcess = process.Start(); + results.Add(projectProcess); + + process.Dispose(); } - return arguments.ToString(); + return results.TrueForAll(p => p); } - + } diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs index 77638b912a..2655dda0b5 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs @@ -1,4 +1,3 @@ -using System.Diagnostics; using Microsoft.Win32; namespace starsky.foundation.native.OpenApplicationNative.Helpers; @@ -29,19 +28,6 @@ public static class WindowsSetFileAssociations private const int SHCNE_ASSOCCHANGED = 0x8000000; private const int SHCNF_FLUSH = 0x1000; - public static void EnsureAssociationsSet() - { - var filePath = Process.GetCurrentProcess().MainModule.FileName; - EnsureAssociationsSet( - new FileAssociation - { - Extension = ".ucs", - ProgId = "UCS_Editor_File", - FileTypeDescription = "UCS File", - ExecutableFilePath = filePath - }); - } - public static void EnsureAssociationsSet(params FileAssociation[] associations) { bool madeChanges = false; @@ -63,7 +49,7 @@ public static void EnsureAssociationsSet(params FileAssociation[] associations) public static bool SetAssociation(string extension, string progId, string fileTypeDescription, string applicationFilePath) { - bool madeChanges = false; + var madeChanges = false; madeChanges |= SetKeyDefaultValue(@"Software\Classes\" + extension, progId); madeChanges |= SetKeyDefaultValue(@"Software\Classes\" + progId, fileTypeDescription); madeChanges |= SetKeyDefaultValue($@"Software\Classes\{progId}\shell\open\command", @@ -71,7 +57,7 @@ public static bool SetAssociation(string extension, string progId, string fileTy return madeChanges; } - private static bool SetKeyDefaultValue(string keyPath, string value) + internal static bool SetKeyDefaultValue(string keyPath, string value) { using ( var key = Registry.CurrentUser.CreateSubKey(keyPath) ) { @@ -81,7 +67,6 @@ private static bool SetKeyDefaultValue(string keyPath, string value) return true; } } - return false; } } diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs b/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs index 2740cd8b00..9e6c1ee11b 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs @@ -25,4 +25,20 @@ public class OpenApplicationNativeService : IOpenApplicationNativeService return macOsOpenResult ?? windowsOpenResult; } + + /// + /// + /// + /// full path style + /// operation succeed (NOT if file is gone) + public bool? OpenDefault(List fullPaths) + { + var currentPlatform = OperatingSystemHelper.GetPlatform(); + var macOsOpenResult = MacOsOpenUrl.OpenDefault(fullPaths , currentPlatform); + + var windowsOpenResult = WindowsOpenDesktopApp.OpenDefault(fullPaths, + currentPlatform); + + return macOsOpenResult ?? windowsOpenResult; + } } diff --git a/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/MacOsStub.cpp b/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/MacOsStub.cpp new file mode 100644 index 0000000000..afb21bb89a --- /dev/null +++ b/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/MacOsStub.cpp @@ -0,0 +1,41 @@ +#include "MacOsStub.h" + +#ifdef __cplusplus +extern "C" { +#endif + + __declspec(dllexport) void* CFStringCreateWithBytes(void* allocator, void* buffer, long bufferLength, void* encoding, bool isExternalRepresentation) { + return nullptr; // Stub implementation + } + + __declspec(dllexport) void* CreateCfString(const char* aString) { + return nullptr; // Stub implementation + } + + __declspec(dllexport) void* CreateCfArray(void** objects, long numObjects) { + return nullptr; // Stub implementation + } + + __declspec(dllexport) void CFRelease(void* handle) { + // Stub implementation + } + + __declspec(dllexport) void* objc_getClass(const char* name) { + return nullptr; // Stub implementation + } + + __declspec(dllexport) void* NSSelectorFromString(void* cfstr) { + return nullptr; // Stub implementation + } + + __declspec(dllexport) void* objc_msgSend_retIntPtr(void* target, void* selector) { + return nullptr; // Stub implementation + } + + __declspec(dllexport) void objc_msgSend_retVoid_IntPtr_IntPtr(void* target, void* selector, void* param1, void* param2) { + // Stub implementation + } + +#ifdef __cplusplus +} +#endif diff --git a/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/MacOsStub.dll b/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/MacOsStub.dll new file mode 100644 index 0000000000000000000000000000000000000000..3bb47bb60bc17ea30d7d9cf1b643f73c538eb532 GIT binary patch literal 80896 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

uF3v!Z*^GlN^PrXK;d#w1C`AaZ0c~VyKRDI9K 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 DvA8W( literal 0 HcmV?d00001 diff --git a/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/MacOsStub.h b/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/MacOsStub.h new file mode 100644 index 0000000000..ac0e48291b --- /dev/null +++ b/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/MacOsStub.h @@ -0,0 +1,23 @@ +#ifndef MACOS_STUB_H +#define MACOS_STUB_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + __declspec(dllexport) void* CFStringCreateWithBytes(void* allocator, void* buffer, long bufferLength, void* encoding, bool isExternalRepresentation); + __declspec(dllexport) void* CreateCfString(const char* aString); + __declspec(dllexport) void* CreateCfArray(void** objects, long numObjects); + __declspec(dllexport) void CFRelease(void* handle); + __declspec(dllexport) void* objc_getClass(const char* name); + __declspec(dllexport) void* NSSelectorFromString(void* cfstr); + __declspec(dllexport) void* objc_msgSend_retIntPtr(void* target, void* selector); + __declspec(dllexport) void objc_msgSend_retVoid_IntPtr_IntPtr(void* target, void* selector, void* param1, void* param2); + +#ifdef __cplusplus +} +#endif + +#endif // MACOS_STUB_H diff --git a/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/readme.md b/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/readme.md new file mode 100644 index 0000000000..3d9c4a7499 --- /dev/null +++ b/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/readme.md @@ -0,0 +1,25 @@ +Visual studio installer -> Modify +-> Desktop Enviorment with C++ + + +To compile the C++ code using the command line on Windows, you can use the Visual Studio Command Prompt, which sets up the necessary environment variables and paths for using the Visual C++ compiler (cl.exe) and other tools. Here's how you can compile the code: + + Open the Visual Studio Command Prompt: + Press the Windows key and type "Visual Studio Command Prompt". + Open the command prompt with the appropriate version of Visual Studio you're using (e.g., Developer Command Prompt for Visual Studio 2019). + + Navigate to the directory containing your C++ source files: + Use the cd command to change to the directory where MacOsStub.cpp and MacOsStub.h are located. + + Compile the code: + Use the cl command to compile the C++ code and generate the DLL. Here's a basic command: + + cl /EHsc /LD /arch=arm64 MacOsStub.cpp + + /EHsc enables standard C++ exception handling. + /LD specifies that the output should be a DLL. + MacOsStub.cpp is the name of your C++ source file. + +Verify the output: + + After a successful compilation, you should find the compiled DLL in the same directory as your source files. \ No newline at end of file diff --git a/starsky/starskytest/starsky.foundation.native/Helpers/OperatingSystemHelperTest.cs b/starsky/starskytest/starsky.foundation.native/Helpers/OperatingSystemHelperTest.cs index 4c8152c8a2..a90020a17a 100644 --- a/starsky/starskytest/starsky.foundation.native/Helpers/OperatingSystemHelperTest.cs +++ b/starsky/starskytest/starsky.foundation.native/Helpers/OperatingSystemHelperTest.cs @@ -13,9 +13,7 @@ public void OperatingSystemHelper1() var result = OperatingSystemHelper.GetPlatform(); Assert.IsNotNull(result); } - - - + [TestMethod] public void OperatingSystemHelper_Windows() { diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs index 12b711190c..e0bee562aa 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; @@ -6,6 +8,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.foundation.native.Helpers; using starsky.foundation.native.OpenApplicationNative.Helpers; +using starsky.foundation.storage.Services; using starskytest.FakeCreateAn; namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers; @@ -13,14 +16,27 @@ namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers; [TestClass] public class MacOsOpenUrlTests { - // [TestMethod] - // public void TEst() - // { - // MacOsOpenUrl.OpenApplicationAtUrl( - // "/Users/dion/data/testcontent/20221029_101722_DSC05623.arw", - // "/Applications/Adobe Photoshop 2024/Adobe Photoshop 2024.app"); - // Thread.Sleep(50); - // } + + [TestMethod] + public void OpenDefault_NonMacOS() + { + var result = MacOsOpenUrl.OpenDefault(["any value"], OSPlatform.Linux); + Assert.IsNull(result); + } + + [TestMethod] + [ExpectedException(typeof(DllNotFoundException))] + public void OpenDefault__NonMacOS() + { + if ( OperatingSystemHelper.GetPlatform() == OSPlatform.OSX ) + { + Assert.Inconclusive("This test if for Windows / Linux only"); + return; + } + + MacOsOpenUrl.OpenDefault(["not important"], OSPlatform.OSX); + } + private const string ConsoleApp = "/System/Applications/Utilities/Console.app"; private const string ConsoleName = "Console"; @@ -58,6 +74,7 @@ await Command.Run("osascript", "-e", } [TestMethod] + [ExpectedException(typeof(DllNotFoundException))] public void TestMethodWithDefaultApp__MacOnly() { if ( OperatingSystemHelper.GetPlatform() != OSPlatform.OSX ) @@ -66,7 +83,67 @@ public void TestMethodWithDefaultApp__MacOnly() return; } - var result = MacOsOpenUrl.OpenDefault("urlNotFound"); + var result = MacOsOpenUrl.OpenDefault(["urlNotFound"]); Assert.IsFalse(result); } + + [TestMethod] + public void OpenApplicationAtUrl_NonMacOs() + { + var result = MacOsOpenUrl.OpenApplicationAtUrl(new List { "any value" }, "app", OSPlatform.Linux); + Assert.IsNull(result); + } + + [TestMethod] + [ExpectedException(typeof(DllNotFoundException))] + public void OpenApplicationAtUrl__NonMacOS() + { + if ( OperatingSystemHelper.GetPlatform() == OSPlatform.OSX ) + { + Assert.Inconclusive("This test if for Windows / Linux only"); + return; + } + + MacOsOpenUrl.OpenApplicationAtUrl(["not important"], "not important", OSPlatform.OSX); + } + + [TestMethod] + [ExpectedException(typeof(DllNotFoundException))] + public void OpenURLsWithApplicationAtURL__NonMacOS() + { + if ( OperatingSystemHelper.GetPlatform() == OSPlatform.OSX ) + { + Assert.Inconclusive("This test if for Windows / Linux only"); + return; + } + + MacOsOpenUrl.OpenURLsWithApplicationAtURL(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + } + + [TestMethod] + [ExpectedException(typeof(DllNotFoundException))] + public void NsWorkspaceSharedWorkSpace__NonMacOS() + { + if ( OperatingSystemHelper.GetPlatform() == OSPlatform.OSX ) + { + Assert.Inconclusive("This test if for Windows / Linux only"); + return; + } + + MacOsOpenUrl.NsWorkspaceSharedWorkSpace(); + } + + [TestMethod] + [ExpectedException(typeof(DllNotFoundException))] + public void InvokeOpenUrl__NonMacOS() + { + if ( OperatingSystemHelper.GetPlatform() == OSPlatform.OSX ) + { + Assert.Inconclusive("This test if for Windows / Linux only"); + return; + } + + MacOsOpenUrl.InvokeOpenUrl(IntPtr.Zero); + } + } diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs index 900b3fe6d9..2b2f9211ec 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs @@ -1,15 +1,61 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.Win32; using starsky.foundation.native.OpenApplicationNative.Helpers; using starsky.foundation.platform.Models; using starskytest.FakeCreateAn.CreateFakeStarskyExe; +using System.Collections.Generic; +using System.Runtime.InteropServices; namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers; [TestClass] public class WindowsOpenDesktopAppTests { + + private const string Extension = ".starsky"; + private const string ProgId = "starskytest"; + private const string FileTypeDescription = "Starsky Test File"; + + + [TestInitialize] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", + "CA1416:Validate platform compatibility", Justification = "Check does exists")] + public void TestInitialize() + { + if ( !new AppSettings().IsWindows ) + { + return; + } + + // Ensure no keys exist before the test starts + Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{Extension}", false); + Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{ProgId}", false); + } + + [TestCleanup] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", + "CA1416:Validate platform compatibility", Justification = "Check does exists")] + public void TestCleanup() + { + if ( !new AppSettings().IsWindows ) + { + return; + } + + // Cleanup created keys + Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{Extension}", false); + Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{ProgId}", false); + } + [TestMethod] - public void OpenDefault_HappyFlow() + public void W_OpenDefault_NonWindows() + { + var result = WindowsOpenDesktopApp.OpenDefault(["any value"], OSPlatform.Linux); + Assert.IsNull(result); + } + + [TestMethod] + public void w_OpenDefault_HappyFlow__WindowsOnly() { if ( !new AppSettings().IsWindows ) { @@ -22,18 +68,46 @@ public void OpenDefault_HappyFlow() WindowsSetFileAssociations.EnsureAssociationsSet( new FileAssociation { - Extension = ".starsky", - ProgId = "starskytest", - FileTypeDescription = "Starsky Test File", + Extension = Extension, + ProgId = ProgId, + FileTypeDescription = FileTypeDescription, ExecutableFilePath = filePath }); - var result = WindowsOpenDesktopApp.OpenDefault(mock.StarskyDotStarskyPath); + var result = WindowsOpenDesktopApp.OpenDefault([mock.StarskyDotStarskyPath], OSPlatform.Windows); Assert.IsTrue(result); } [TestMethod] - public void OpenDefault_FileNotFound() + public void W_OpenApplicationAtUrl_NonWindows() + { + var result = WindowsOpenDesktopApp.OpenApplicationAtUrl(new List { "any value" }, "app", OSPlatform.Linux); + Assert.IsNull(result); + } + + [TestMethod] + public void W_OpenApplicationAtUrl_ReturnsTrue_WhenApplicationOpens() + { + // Arrange + var mock = new CreateFakeStarskyExe(); + + var fileUrls = new List + { + mock.StarskyDotStarskyPath, + }; + + // @"C:\Windows\System32\notepad.exe"; // Example application URL (notepad.exe) + + // Act + var result = WindowsOpenDesktopApp.OpenApplicationAtUrl(fileUrls, mock.FullFilePath); + + // Assert + Assert.IsTrue(result); + } + + + [TestMethod] + public void W_OpenDefault_FileNotFound__WindowsOnly() { if ( !new AppSettings().IsWindows ) { @@ -41,7 +115,7 @@ public void OpenDefault_FileNotFound() return; } - var result = WindowsOpenDesktopApp.OpenDefault("not-found"); + var result = WindowsOpenDesktopApp.OpenDefault(["C:\\not-found-74537587345853847345"], OSPlatform.Windows); Assert.IsFalse(result); } } diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs index a5a4c26a66..fc36cfd6fe 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs @@ -1,12 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.foundation.native.OpenApplicationNative.Helpers; -using starsky.foundation.storage.Services; using starskytest.FakeCreateAn.CreateFakeStarskyExe; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers { diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs new file mode 100644 index 0000000000..5e7a51b98d --- /dev/null +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs @@ -0,0 +1,88 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.Win32; +using starsky.foundation.native.OpenApplicationNative; +using starsky.foundation.native.OpenApplicationNative.Helpers; +using starsky.foundation.platform.Models; +using starskytest.FakeCreateAn.CreateFakeStarskyExe; + +namespace starskytest.starsky.foundation.native.OpenApplicationNative +{ + + [TestClass] + public class OpenApplicationNativeServiceTest + { + private const string Extension = ".starsky"; + private const string ProgId = "starskytest"; + private const string FileTypeDescription = "Starsky Test File"; + + [TestInitialize] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", + "CA1416:Validate platform compatibility", Justification = "Check does exists")] + public void TestInitialize() + { + if ( !new AppSettings().IsWindows ) + { + return; + } + + // Ensure no keys exist before the test starts + Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{Extension}", false); + Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{ProgId}", false); + } + + [TestCleanup] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", + "CA1416:Validate platform compatibility", Justification = "Check does exists")] + public void TestCleanup() + { + if ( !new AppSettings().IsWindows ) + { + return; + } + + // Cleanup created keys + Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{Extension}", false); + Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{ProgId}", false); + } + + [TestMethod] + public void OpenDefault_HappyFlow__WindowsOnly() + { + if ( !new AppSettings().IsWindows ) + { + Assert.Inconclusive("This test if for Windows Only"); + return; + } + + var mock = new CreateFakeStarskyExe(); + var filePath = mock.FullFilePath; + WindowsSetFileAssociations.EnsureAssociationsSet( + new FileAssociation + { + Extension = Extension, + ProgId = ProgId, + FileTypeDescription = FileTypeDescription, + ExecutableFilePath = filePath + }); + + var result = new OpenApplicationNativeService().OpenDefault([mock.StarskyDotStarskyPath]); + Assert.IsTrue(result); + } + + + [TestMethod] + public void OpenApplicationAtUrl_ZeroItemsSoFalse() + { + var result = new OpenApplicationNativeService().OpenApplicationAtUrl([], "app"); + Assert.IsFalse(result); + } + + + [TestMethod] + public void OpenDefault_ZeroItemsSoFalse() + { + var result = new OpenApplicationNativeService().OpenDefault([]); + Assert.IsFalse(result); + } + } +} diff --git a/starsky/starskytest/starsky.foundation.platform/Helpers/PathHelperTest.cs b/starsky/starskytest/starsky.foundation.platform/Helpers/PathHelperTest.cs index 4f3143822d..11f62d3c5d 100644 --- a/starsky/starskytest/starsky.foundation.platform/Helpers/PathHelperTest.cs +++ b/starsky/starskytest/starsky.foundation.platform/Helpers/PathHelperTest.cs @@ -39,7 +39,7 @@ public async Task GetFileName_ReturnsFileName_WithMaliciousInput_UnixOnly() new MemoryStream(CreateAnImageA6600.Bytes.ToArray())); var result = string.Empty; - for ( var i = 0; i < 100; i++ ) + for ( var i = 0; i < 200; i++ ) { result += test + test2 + test + test; } From 539820f4f2584c99c1afcdf9dc44a4d2b7b4cf80 Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 14 Feb 2024 18:24:34 +0100 Subject: [PATCH 014/125] rename --- .../Helpers/WindowsOpenDesktopAppTests.cs | 3 +-- .../OpenApplicationNative/OpenApplicationNativeServiceTest.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs index 2b2f9211ec..07da1e9ec9 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs @@ -55,7 +55,7 @@ public void W_OpenDefault_NonWindows() } [TestMethod] - public void w_OpenDefault_HappyFlow__WindowsOnly() + public void W_OpenDefault_HappyFlow__WindowsOnly() { if ( !new AppSettings().IsWindows ) { @@ -105,7 +105,6 @@ public void W_OpenApplicationAtUrl_ReturnsTrue_WhenApplicationOpens() Assert.IsTrue(result); } - [TestMethod] public void W_OpenDefault_FileNotFound__WindowsOnly() { diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs index 5e7a51b98d..1a2577ca4b 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs @@ -71,7 +71,7 @@ public void OpenDefault_HappyFlow__WindowsOnly() [TestMethod] - public void OpenApplicationAtUrl_ZeroItemsSoFalse() + public void OpenApplicationAtUrl_ZeroItems_SoFalse() { var result = new OpenApplicationNativeService().OpenApplicationAtUrl([], "app"); Assert.IsFalse(result); @@ -79,7 +79,7 @@ public void OpenApplicationAtUrl_ZeroItemsSoFalse() [TestMethod] - public void OpenDefault_ZeroItemsSoFalse() + public void OpenDefault_ZeroItemsSo_False() { var result = new OpenApplicationNativeService().OpenDefault([]); Assert.IsFalse(result); From 9552be940dc544b9fbaddab744a6fcc561e54d35 Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 14 Feb 2024 19:42:48 +0100 Subject: [PATCH 015/125] Fix tests on Mac OS --- .../Helpers/MacOsOpenUrl.cs | 67 ++++++---- .../Helpers/WindowsOpenDesktopApp.cs | 43 +++--- .../Helpers/WindowsSetFileAssociations.cs | 30 +++-- .../OpenApplicationNativeService.cs | 5 +- .../CreateFakeStarskyExe.cs | 40 ------ .../CreateFakeStarskyUnixBash.cs | 22 +++ .../CreateFakeStarskyWindowsExe.cs | 21 +++ .../CreateFakeStarskyExe/starsky-macos.zip | Bin 0 -> 24172 bytes .../Helpers/MacOsOpenUrlTests.cs | 23 +++- .../Helpers/WindowsOpenDesktopAppTests.cs | 77 +++++++---- .../WindowsSetFileAssociationsTests.cs | 17 ++- .../OpenApplicationNativeServiceTest.cs | 9 +- starsky/starskytest/starskytest.csproj | 126 +++++++++--------- 13 files changed, 277 insertions(+), 203 deletions(-) delete mode 100644 starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyExe.cs create mode 100644 starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyUnixBash.cs create mode 100644 starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyWindowsExe.cs create mode 100644 starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/starsky-macos.zip diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs index 28d23cbaa9..9397fc8b76 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrl.cs @@ -1,8 +1,12 @@ +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using starsky.foundation.native.Trash.Helpers; namespace starsky.foundation.native.OpenApplicationNative.Helpers; +[SuppressMessage("Interoperability", + "SYSLIB1054:Use \'LibraryImportAttribute\' instead of \'DllImportAttribute\' to " + + "generate P/Invoke marshalling code at compile time")] public static class MacOsOpenUrl { ///

@@ -25,6 +29,11 @@ public static class MacOsOpenUrl public static bool OpenDefault( List fileUrls) { + if ( fileUrls.Count == 0 ) + { + return false; + } + var fileUrlsIntPtr = MacOsTrashBindingHelper.GetUrls(fileUrls); var result = new List(); @@ -36,14 +45,6 @@ public static bool OpenDefault( return result.TrueForAll(p => p); } - internal static bool InvokeOpenUrl(IntPtr fileUrlIntPtr) - { - return objc_msgSend_retBool_IntPtr_IntPtr( - NsWorkspaceSharedWorkSpace(), - MacOsTrashBindingHelper.GetSelector("openURL:"), - fileUrlIntPtr); - } - internal static bool? OpenApplicationAtUrl( List fileUrls, string applicationUrl, OSPlatform platform) @@ -57,11 +58,16 @@ internal static bool InvokeOpenUrl(IntPtr fileUrlIntPtr) /// /// Absolute Paths /// Open with .app folder - /// When not Mac OS + /// When not Mac OS internal static bool? OpenApplicationAtUrl( List fileUrls, string applicationUrl) { + if ( fileUrls.Count == 0 ) + { + return false; + } + var filesUrlIntPtr = MacOsTrashBindingHelper.GetUrls(fileUrls); var fileUrlIntPtrUrlArray = MacOsTrashBindingHelper.CreateCfArray(filesUrlIntPtr); @@ -73,32 +79,46 @@ internal static bool InvokeOpenUrl(IntPtr fileUrlIntPtr) nsWorkspaceOpenConfiguration, MacOsTrashBindingHelper.GetSelector("configuration")); // https://developer.apple.com/documentation/appkit/nsworkspace/3172702-openurls?language=objc - OpenURLsWithApplicationAtURL(fileUrlIntPtrUrlArray, applicationUrlIntPtr, nsWorkspaceOpenConfigurationDefault); + OpenUrLsWithApplicationAtUrl(fileUrlIntPtrUrlArray, applicationUrlIntPtr, + nsWorkspaceOpenConfigurationDefault); return true; } + /// + /// Open Default Url + /// + /// Pointer for urls + /// Is Success + internal static bool InvokeOpenUrl(IntPtr fileUrlIntPtr) + { + return objc_msgSend_retBool_IntPtr_IntPtr( + NsWorkspaceSharedWorkSpace(), + MacOsTrashBindingHelper.GetSelector("openURL:"), + fileUrlIntPtr); + } -/// -/// @see: https://developer.apple.com/documentation/appkit/nsworkspace/3172702-openurls?language=objc -/// -internal static void OpenURLsWithApplicationAtURL(nint fileUrlIntPtrUrlArray, nint applicationUrlIntPtr, nint nsWorkspaceOpenConfigurationDefault) -{ + /// + /// @see: https://developer.apple.com/documentation/appkit/nsworkspace/3172702-openurls?language=objc + /// + internal static void OpenUrLsWithApplicationAtUrl(nint fileUrlIntPtrUrlArray, + nint applicationUrlIntPtr, nint nsWorkspaceOpenConfigurationDefault) + { objc_msgSend_retVoid_IntPtr_IntPtr_IntPtr_IntPtr( - NsWorkspaceSharedWorkSpace(), - MacOsTrashBindingHelper.GetSelector( - "openURLs:withApplicationAtURL:configuration:completionHandler:"), - fileUrlIntPtrUrlArray, - applicationUrlIntPtr, - nsWorkspaceOpenConfigurationDefault, - IntPtr.Zero); + NsWorkspaceSharedWorkSpace(), + MacOsTrashBindingHelper.GetSelector( + "openURLs:withApplicationAtURL:configuration:completionHandler:"), + fileUrlIntPtrUrlArray, + applicationUrlIntPtr, + nsWorkspaceOpenConfigurationDefault, + IntPtr.Zero); } private const string FoundationFramework = "/System/Library/Frameworks/Foundation.framework/Foundation"; private const string AppKitFramework = - "/System/Library/Frameworks/AppKit.framework/AppKit"; + "/System/Library/Frameworks/AppKit.framework/AppKit"; [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] private static extern IntPtr objc_msgSend_retIntPtr(IntPtr target, IntPtr selector); @@ -113,6 +133,7 @@ private static extern IntPtr objc_msgSend_retVoid_IntPtr_IntPtr_IntPtr_IntPtr( IntPtr param4); [DllImport(AppKitFramework)] + [SuppressMessage("Globalization", "CA2101:Specify marshaling for P/Invoke string arguments")] static extern IntPtr objc_getClass(string className); [DllImport(FoundationFramework, EntryPoint = "objc_msgSend")] diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs index c2f13f4c6e..eeb1a099de 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopApp.cs @@ -1,18 +1,16 @@ using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; -using System.Text; namespace starsky.foundation.native.OpenApplicationNative.Helpers; public static class WindowsOpenDesktopApp { - /// /// Add check if is Windows /// - /// - /// + /// full file paths + /// running platform /// internal static bool? OpenDefault( List fileUrls, OSPlatform platform) @@ -20,8 +18,7 @@ public static class WindowsOpenDesktopApp return platform != OSPlatform.Windows ? null : OpenDefault(fileUrls); } - public static bool? OpenDefault( - List fileUrls) + public static bool? OpenDefault(List fileUrls) { if ( fileUrls.Count == 0 ) { @@ -37,20 +34,22 @@ public static class WindowsOpenDesktopApp return result.TrueForAll(p => p == true); } - /// - /// Does NOT check if file exists - /// - /// Absolute Path of file - /// + /// + /// Does NOT check if file exists + /// + /// Absolute Path of file + /// public static bool? OpenDefault( string fileUrl) { try { - var projectStartInfo = new ProcessStartInfo(); - projectStartInfo.FileName = fileUrl; - projectStartInfo.UseShellExecute = true; - projectStartInfo.WindowStyle = ProcessWindowStyle.Normal; + var projectStartInfo = new ProcessStartInfo + { + FileName = fileUrl, + UseShellExecute = true, + WindowStyle = ProcessWindowStyle.Normal + }; var projectProcess = Process.Start(projectStartInfo); return projectProcess != null; } @@ -94,15 +93,14 @@ internal static bool OpenApplicationAtUrl( var results = new List(); foreach ( var url in fileUrls ) { - var projectStartInfo = new ProcessStartInfo(); - projectStartInfo.FileName = applicationUrl; - projectStartInfo.WindowStyle = ProcessWindowStyle.Normal; - projectStartInfo.Arguments = url; - - var process = new Process + var projectStartInfo = new ProcessStartInfo { - StartInfo = projectStartInfo + FileName = applicationUrl, + WindowStyle = ProcessWindowStyle.Normal, + Arguments = url }; + + var process = new Process { StartInfo = projectStartInfo }; var projectProcess = process.Start(); results.Add(projectProcess); @@ -111,5 +109,4 @@ internal static bool OpenApplicationAtUrl( return results.TrueForAll(p => p); } - } diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs index 2655dda0b5..090f2e0fe7 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Microsoft.Win32; namespace starsky.foundation.native.OpenApplicationNative.Helpers; @@ -7,10 +8,13 @@ public class FileAssociation public string Extension { get; set; } = string.Empty; public string ProgId { get; set; } = string.Empty; public string FileTypeDescription { get; set; } = string.Empty; - public string ExecutableFilePath { get; set; } = string.Empty; + public string ExecutableFilePath { get; set; } = string.Empty; } - +[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", + Justification = "Check build in")] +[SuppressMessage("ReSharper", "IdentifierTypo")] +[SuppressMessage("ReSharper", "InconsistentNaming")] public static class WindowsSetFileAssociations { /// @@ -23,14 +27,18 @@ public static class WindowsSetFileAssociations /// /// [System.Runtime.InteropServices.DllImport("Shell32.dll")] + [SuppressMessage("Interoperability", "SYSLIB1054:Use \'LibraryImportAttribute\' " + + "instead of \'DllImportAttribute\' to generate P/Invoke " + + "marshalling code at compile time")] private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2); private const int SHCNE_ASSOCCHANGED = 0x8000000; private const int SHCNF_FLUSH = 0x1000; + [SuppressMessage("Performance", "CA1806:Do not ignore method results")] public static void EnsureAssociationsSet(params FileAssociation[] associations) { - bool madeChanges = false; + var madeChanges = false; foreach ( var association in associations ) { madeChanges |= SetAssociation( @@ -46,7 +54,7 @@ public static void EnsureAssociationsSet(params FileAssociation[] associations) } } - public static bool SetAssociation(string extension, string progId, string fileTypeDescription, + internal static bool SetAssociation(string extension, string progId, string fileTypeDescription, string applicationFilePath) { var madeChanges = false; @@ -59,15 +67,9 @@ public static bool SetAssociation(string extension, string progId, string fileTy internal static bool SetKeyDefaultValue(string keyPath, string value) { - using ( var key = Registry.CurrentUser.CreateSubKey(keyPath) ) - { - if ( key.GetValue(null) as string != value ) - { - key.SetValue(null, value); - return true; - } - } - return false; + using var key = Registry.CurrentUser.CreateSubKey(keyPath); + if ( key.GetValue(null) as string == value ) return false; + key.SetValue(null, value); + return true; } } - diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs b/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs index 9e6c1ee11b..bef8259271 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs @@ -34,10 +34,9 @@ public class OpenApplicationNativeService : IOpenApplicationNativeService public bool? OpenDefault(List fullPaths) { var currentPlatform = OperatingSystemHelper.GetPlatform(); - var macOsOpenResult = MacOsOpenUrl.OpenDefault(fullPaths , currentPlatform); - + var macOsOpenResult = MacOsOpenUrl.OpenDefault(fullPaths, currentPlatform); var windowsOpenResult = WindowsOpenDesktopApp.OpenDefault(fullPaths, - currentPlatform); + currentPlatform); return macOsOpenResult ?? windowsOpenResult; } diff --git a/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyExe.cs b/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyExe.cs deleted file mode 100644 index 971c936349..0000000000 --- a/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyExe.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Collections.Immutable; -using System.IO; -using System.Reflection; -using starsky.foundation.storage.Storage; -using starskytest.FakeMocks; - -namespace starskytest.FakeCreateAn.CreateFakeStarskyExe -{ - public class CreateFakeStarskyExe - { - public CreateFakeStarskyExe() - { - var dirName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - if ( string.IsNullOrEmpty(dirName) ) return; - var parentFolder = Path.Combine(dirName, "FakeCreateAn", - "CreateFakeStarskyExe"); - var path = Path.Combine(parentFolder, "starsky.exe"); - FullFilePath = path; - StarskyDotStarskyPath = Path.Combine(parentFolder, "starsky.starsky"); - - Bytes = [.. StreamToBytes(path)]; - } - - public string FullFilePath { get; set; } = string.Empty; - public string StarskyDotStarskyPath { get; set; } = string.Empty; - - private static byte[] StreamToBytes(string path) - { - var input = new StorageHostFullPathFilesystem(new FakeIWebLogger()).ReadStream(path); - using var ms = new MemoryStream(); - input.CopyTo(ms); - input.Dispose(); - return ms.ToArray(); - } - - public readonly ImmutableArray Bytes = []; - - } -} - diff --git a/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyUnixBash.cs b/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyUnixBash.cs new file mode 100644 index 0000000000..196f5c7bff --- /dev/null +++ b/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyUnixBash.cs @@ -0,0 +1,22 @@ +using System.IO; +using System.Reflection; + +namespace starskytest.FakeCreateAn.CreateFakeStarskyExe; + +public class CreateFakeStarskyUnixBash +{ + public CreateFakeStarskyUnixBash() + { + var dirName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + if ( string.IsNullOrEmpty(dirName) ) return; + var parentFolder = Path.Combine(dirName, "FakeCreateAn", + "CreateFakeStarskyExe"); + var path = Path.Combine(parentFolder, "starsky"); + FullFilePath = path; + StarskyDotStarskyPath = Path.Combine(parentFolder, "starsky.starsky"); + } + + public string StarskyDotStarskyPath { get; set; } = string.Empty; + + public string FullFilePath { get; set; } = string.Empty; +} diff --git a/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyWindowsExe.cs b/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyWindowsExe.cs new file mode 100644 index 0000000000..eb71700fc4 --- /dev/null +++ b/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyWindowsExe.cs @@ -0,0 +1,21 @@ +using System.IO; +using System.Reflection; + +namespace starskytest.FakeCreateAn.CreateFakeStarskyExe; + +public class CreateFakeStarskyWindowsExe +{ + public CreateFakeStarskyWindowsExe() + { + var dirName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + if ( string.IsNullOrEmpty(dirName) ) return; + var parentFolder = Path.Combine(dirName, "FakeCreateAn", + "CreateFakeStarskyExe"); + var path = Path.Combine(parentFolder, "starsky.exe"); + FullFilePath = path; + StarskyDotStarskyPath = Path.Combine(parentFolder, "starsky.starsky"); + } + + public string FullFilePath { get; set; } = string.Empty; + public string StarskyDotStarskyPath { get; set; } = string.Empty; +} diff --git a/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/starsky-macos.zip b/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/starsky-macos.zip new file mode 100644 index 0000000000000000000000000000000000000000..29f854c19b998eee88d3a0f199a097628f7758f7 GIT binary patch literal 24172 zcmbrk1yCKqx-AUBAxMxA9D=(9cXxM!2X}Y&-U%AqT{iBn!7Vt!ogl$oH?XnIpL6d0 z&wX{@J@wc7Usv_iOij=Fy4S3)XR3RtD{}*t|f8&+n-vMh{ zdANAFTUh-kFX(@P|55)fFQ{;+aQ`bfDiR#rzXextwsv82b+q&F{O5bb_zV1RdY+SP z>XOBQKL}1!>+_8xn=Q_y%dGgO9Vtt~eK;?o!sd<3Jap%n6yhQlUIcohpLrK{^*{y= zXyfk`NuF4HS6sfu2ZWRDmh+|J*gGM4W$d8<-nx_qUwU*#Uz;BNPKvhQDOPK=eCG$l z=@()o`o=M7!LIV&;M@Qk^&qbMz$H3ilk)e)Jsg=%9?qUdnR!JB&DeYd56NdZvtU5L z#OTY%SU{AWc}cL^pVav|0~sQq_+XfO!$hVIY8;8TDT$SZ+OXc$Eknq`LPpfN4Z(2q zPfNMCLkd`57mza1q;J34UPnih&o9u65`uQSI zIqGiQ`m+d?IPI64`RbMf9@o3AcPXz1l97?DpOZRM$$~q$RgCmo-i>8@SjQBopqw`u z&=wD(sSIT+-lq{fb&9snVoPNb?nK>Q43u zuFvxKT#@W534OYB(#&CT49yC{UQ^Xr)nXVwb5D95PVRX!4lm!N=Ov-K5(?@BEv?}M zJRICV<#GATYpCNjqsW$K(895=8ClBgXeGU)Vi-lvIEZ3>ce{Q3Ru6oDbIac=YDKQ zwv(sM9tYepjc%+@eLHp{2h??S(bw^)5qtJAhpT`m1nLMA`Y?IQ09RkOgf1a z)p+ADQxxx`kW3i^I<-0jby36{_g&6RcswP&#M@mIS(=Z=VdcxFqY16=utL`b8}{p( zz#Q<-R90godlI!q!Rw-_uP+(G%u54$TiiBOu1(0K20?pYy(Oa0=P8K_ds=!O+R>Fi zQ4ON(4)zS{Qla-=DLXz8A^zljdg%_;E%_+p^^p%96%(vvRfX=O$;{YSP)~ZpAwUQp z-m0jXR@X)^X>9Fq#ST>gp>&W`p}&8)Uph;|^&(c}K0#`UVLN_XI<)+HgfX^dTq(9J z^!AMTJ&n3L??JH`#s_IbLh?}y`szvY)FD2XT?9k3mFjB@oHpOiPON^z9?vUkjAh*B zDG!Mxachl7A_8efCS&5A`FHV`s--QBjIQt+HyX85zYZ6j^OHnpj>JCP_K^#02-)bt|A)5zXVuj;zJX)>cdGl3Y5hMxc$QWk4xTQq|5BUr z|1A1507jse%-t)Eoe~4mSO==SZ&{hRp)Cf3{A^Ray%GlN-k&e>No%}`bX0Bmtlg1$ zxDKGTM|}eyy%}%^#$nB2MO2OAI|}IXUVVGP$rlOz;5cJXfI7jon9X@n+x`Abd;8PF zGYtH2_sM6n&0~Ke)~uWU{(UWri6(ia)LgiGS+4fH zmQH6UlPa)~31LUeV8pVuT35psxA2QSAY*_A5HKY3fZw)(Kl{<9TDRvz*%ke~4~N|B z!J@RE07Tg|Qxe>4;_8QFN(#8Kk$lhFeCzZp3F7p(r3^sF?D4n? zCTmm8Fm#xuNqHnhyVI<2t0D8CTTjHqGGB3P^(zswo~Lo$bzJF%&L$TuRqZnF$k?MX zbU!Lu_iK+3U=Ik-==LN6zZH3O1ojNG@mAiF**v;i27*s-9)%0&TL9rx2@tChsAy}r zm@ZhNcxqY?5M&7989vS$gB)3PDw>vLe}kb^Z+R}=UMd1)OKRz8$Ht!;S9ObDpE)1N zv+0kN60Sy1NL={@*Rd++j87sgHhD{?$|9~uZ_#cSKZC0!zMF1NTP1e0s3b3qUSm@h znbcq(Xpo-hIwX{jS@NW@S0pzP>D-htE!KYpjud&%`&rJ}N&>XFHALJyT)VSMM{09R zlOIdc^~KB|Hr>Dpnov|q5-yJuT<5xfGm^Ln7dBDKnYjOM}o*wK5f`j&huQy;JqGa;&yd?7)L{YD4v9|GCy%R`oLbIm`n7 z;sQ|=&TVcjF90V8ERUp-ns0vXE;h8qDhYgdzzYjzL`YaHJ^i|wcILilDU@l9l`0yx z;`WkZ3TgsPM?caxshF~5^JY$%JroHB>8wg5mvql(FBnTFDHFfj7~pMc42Ziu^rxZ=s=_Do$}P+fk(lTo&pZ z3`t9OYC4NqSf7d*%;+jk#s6>gS{SeIoE1p6kEf9@~Qj`#4J-9(&Nb)ntpja;8J6?!UMvwI0S zh;)ZBEuW_Hn?)Um3?qZZ>;htytf#Kg8{Lg8fO~=Q8HqGK!{BLbg(ry#eJJ11oFw>{ z;YHIE0l^0h|F_aS;8~U#2x+P_ekNjoc+W zW-K0U5onEyZz{ph4^!)#P4$2Elyt{Ft0oif{vKbPx`n1>2=hxq^&^(MtVnVJE6JZh z`gZs<{FM1vKj~zpW>zq}14CJ$NSStpxr?&9su-O^gzV#|)3Q-IKp#X->QHUB8A6w)f6{AzP zx~Cn=nNlR_H-N(XceEFC9Cxx|6O&BDXkt{*+USy=6|ig~<-wIx4$CSFQh3cb!IT#CHSrp5sKjBno9`32xUVr^j*}LeQp{6_ z!F~Ex?@%Mpj`Z^#`QfS!8hL$FjBhi1w7ksJX$vY_noULi%ystgfx9y0Zjw5auI0{f zSl_rH3bsPVv-`|0OLhdS`1IM<7QJabqG=1_D{f#j_m5Kk)<*vO6m{FT*8Y9!xPj#~ z&{RgKKO=WR`Q8s?bT%iaLl#Wfz$~w`8^Y&n)kDT=OLqq;A-diiQ-^x$Us*A!A|b^q zF86Z-!!tB7$`a~WGT)U2+}f%I}nQ3xtht_JV}8OWAXEi!ci9M%Y4 z+TIAc*crkk30XxU}A>|_w+@=&dZDfO>fP9RvNlL-sQ1rff+Plys zl`K%QVxFH$a5#SP@evP3Ly_u?`U<<0DyA19q!3}+nD>hxh{~;V0hee>{bCWAnkAU^ zC_z7M+Gbx%3yqsDrP-B~)>~UxN7Q_`zt7;d>oC)`S|Tb zurq{XI{zW$u0owUCQ6D@%L|@X&EWIFO1z^XasMB8QC8+#6MCnDD(0tY*p%U2Am-U? z>>zNgK%hhDZGT&BZ^%1;YNcbOaO5~X#S<9^`wYv>GUHG{Ck636oYQwdl5z?5Q1YTS zO?oL@?k{RjtYHl(8(+Ejx^NF|>`1<2FaHT1*Q>i7;lgg!OKq7QHR#qz#iUPIo8RP$ z*(kP)p=TvLUzrE7M?AW?WZza);9B$5o%z&7VHD@UubH_v*M!lQ**0+Ei(BjgE`z6S z=F0;p$9a_m#vT`3Pa4Y3||xtF&} z9>+5`7EAYA>AAOE0Y&}*louaHh)a}(%pW37`3XR7M}YSnT@*Y!)ZKj;f1W~vkG zV2X%-%N(vzdnFCj%At+whl?76gGJgessti#FKQMEhz6F++4btnd#gbfDzd0$yIQ(wpt*+T7vrie71@OUn35aYA1Vq#pEe zG5-$Up4AUC^lQ=g>YHLIYSNh>=yLyljGgif)!$n8=2Pg30ojC|Zw%-SuF zc5T9|TKzPaB1I6SbQ=ATcd%_(j5wx5jOC0*@{5>mDW}w>EMYWrlq1jSk!fn58i>oM zynJ@n)LalZ*{xnf@O?r@eU;Dyyp*KdNcF>JYP2dwE?s%lTnrPKtpq9d6vJjBQ#`AFdou4rsXF0z zt_v~nn3$XZ`k=4!9b;;?;qqG@KdMufLA2F&wAE1%tLk$WBd3DQ*s+7!#F%wFZ=yPA zU+9x=C|Qyf`@q5t;UU_6H0JnL+BmZ-36ZdV_7knD7>D^8vlE_R&uXxR6e)(#wX5Cb zMgBWM!GzPKa}?G79Vwd?71KyN+P=yL(RNF|RC3~^h^NIg7WO$Go?@rl%ib$BqYX1r zCOySiYoaUvFhC_Rhm*=;GJI zqsqCR&1KhHbFkL?91pk2=JrGJrvG9f%CQ-$E+HuRZE;ULNNr`XCVy*bHb%O+4#-z7 zGMua1K@8iDxt}fW6ZPSsq9``#e^Lyxi@&Nv;OYiXwuoM{)?I~P-?Q@oF7Ks+x1WTV zMHV-c=0G1na|)uz7x}0AJ=-0fEynv7PiJN~Q%`a)UCljXo4K&W9#e|4OCJ$y$i3R> zMefV}kl!Z>Ueez6GwF_|(DaXIFgJwQq@~V$@Ail38C%5eR*dT5YZL4R%;bxnwd=U( zUX=ATb2qebUCg2^?UsC%XV{z7CnekK*_Ipf%0AL1GN|phFM;9r=AU{C4BtQhz<-b9 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 O;xcwI!AOhCTmJ*;ufRnB literal 0 HcmV?d00001 diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs index e0bee562aa..acf2218c1f 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs @@ -8,7 +8,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.foundation.native.Helpers; using starsky.foundation.native.OpenApplicationNative.Helpers; -using starsky.foundation.storage.Services; using starskytest.FakeCreateAn; namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers; @@ -16,7 +15,6 @@ namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers; [TestClass] public class MacOsOpenUrlTests { - [TestMethod] public void OpenDefault_NonMacOS() { @@ -74,7 +72,20 @@ await Command.Run("osascript", "-e", } [TestMethod] - [ExpectedException(typeof(DllNotFoundException))] + public void OpenApplicationAtUrl_NoItems() + { + var result = MacOsOpenUrl.OpenApplicationAtUrl([], ConsoleApp); + Assert.IsFalse(result); + } + + [TestMethod] + public void OpenDefault_NoItems() + { + var result = MacOsOpenUrl.OpenDefault([]); + Assert.IsFalse(result); + } + + [TestMethod] public void TestMethodWithDefaultApp__MacOnly() { if ( OperatingSystemHelper.GetPlatform() != OSPlatform.OSX ) @@ -90,7 +101,8 @@ public void TestMethodWithDefaultApp__MacOnly() [TestMethod] public void OpenApplicationAtUrl_NonMacOs() { - var result = MacOsOpenUrl.OpenApplicationAtUrl(new List { "any value" }, "app", OSPlatform.Linux); + var result = MacOsOpenUrl.OpenApplicationAtUrl(new List { "any value" }, "app", + OSPlatform.Linux); Assert.IsNull(result); } @@ -117,7 +129,7 @@ public void OpenURLsWithApplicationAtURL__NonMacOS() return; } - MacOsOpenUrl.OpenURLsWithApplicationAtURL(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + MacOsOpenUrl.OpenUrLsWithApplicationAtUrl(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); } [TestMethod] @@ -145,5 +157,4 @@ public void InvokeOpenUrl__NonMacOS() MacOsOpenUrl.InvokeOpenUrl(IntPtr.Zero); } - } diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs index 07da1e9ec9..411998b36a 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs @@ -5,48 +5,45 @@ using starskytest.FakeCreateAn.CreateFakeStarskyExe; using System.Collections.Generic; using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Medallion.Shell; namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers; [TestClass] public class WindowsOpenDesktopAppTests { - private const string Extension = ".starsky"; private const string ProgId = "starskytest"; private const string FileTypeDescription = "Starsky Test File"; - [TestInitialize] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", - "CA1416:Validate platform compatibility", Justification = "Check does exists")] public void TestInitialize() { - if ( !new AppSettings().IsWindows ) - { - return; - } - - // Ensure no keys exist before the test starts - Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{Extension}", false); - Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{ProgId}", false); + CleanSetup(); } [TestCleanup] + public void TestCleanup() + { + CleanSetup(); + } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Check does exists")] - public void TestCleanup() + private static void CleanSetup() { if ( !new AppSettings().IsWindows ) { return; } - // Cleanup created keys + // Ensure no keys exist before the test starts Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{Extension}", false); Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{ProgId}", false); } + [TestMethod] public void W_OpenDefault_NonWindows() { @@ -63,7 +60,7 @@ public void W_OpenDefault_HappyFlow__WindowsOnly() return; } - var mock = new CreateFakeStarskyExe(); + var mock = new CreateFakeStarskyWindowsExe(); var filePath = mock.FullFilePath; WindowsSetFileAssociations.EnsureAssociationsSet( new FileAssociation @@ -74,31 +71,57 @@ public void W_OpenDefault_HappyFlow__WindowsOnly() ExecutableFilePath = filePath }); - var result = WindowsOpenDesktopApp.OpenDefault([mock.StarskyDotStarskyPath], OSPlatform.Windows); + var result = + WindowsOpenDesktopApp.OpenDefault([mock.StarskyDotStarskyPath], OSPlatform.Windows); Assert.IsTrue(result); } [TestMethod] public void W_OpenApplicationAtUrl_NonWindows() { - var result = WindowsOpenDesktopApp.OpenApplicationAtUrl(new List { "any value" }, "app", OSPlatform.Linux); + var result = WindowsOpenDesktopApp.OpenApplicationAtUrl(new List { "any value" }, + "app", OSPlatform.Linux); Assert.IsNull(result); } [TestMethod] - public void W_OpenApplicationAtUrl_ReturnsTrue_WhenApplicationOpens() + public void W_OpenApplicationAtUrl_ReturnsTrue_WhenApplicationOpens__WindowsOnly() { + if ( !new AppSettings().IsWindows ) + { + Assert.Inconclusive("This test if for Windows Only"); + return; + } + // Arrange - var mock = new CreateFakeStarskyExe(); + var mock = new CreateFakeStarskyWindowsExe(); - var fileUrls = new List - { - mock.StarskyDotStarskyPath, - }; + var fileUrls = new List { mock.StarskyDotStarskyPath, }; - // @"C:\Windows\System32\notepad.exe"; // Example application URL (notepad.exe) + // Act + var result = WindowsOpenDesktopApp.OpenApplicationAtUrl(fileUrls, mock.FullFilePath); - // Act + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public async Task W_OpenApplicationAtUrl_ReturnsTrue_WhenApplicationOpens__UnixOnly() + { + if ( new AppSettings().IsWindows ) + { + Assert.Inconclusive("This test if for Unix Only"); + return; + } + + // Arrange + var mock = new CreateFakeStarskyUnixBash(); + var fileUrls = new List { mock.StarskyDotStarskyPath, }; + + await Command.Run("chmod", "+x", + mock.FullFilePath).Task; + + // Act var result = WindowsOpenDesktopApp.OpenApplicationAtUrl(fileUrls, mock.FullFilePath); // Assert @@ -114,8 +137,8 @@ public void W_OpenDefault_FileNotFound__WindowsOnly() return; } - var result = WindowsOpenDesktopApp.OpenDefault(["C:\\not-found-74537587345853847345"], OSPlatform.Windows); + var result = WindowsOpenDesktopApp.OpenDefault(["C:\\not-found-74537587345853847345"], + OSPlatform.Windows); Assert.IsFalse(result); } } - diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs index fc36cfd6fe..5cfd9e77d2 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs @@ -1,5 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.Win32; using starsky.foundation.native.OpenApplicationNative.Helpers; +using starsky.foundation.platform.Models; using starskytest.FakeCreateAn.CreateFakeStarskyExe; namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers @@ -10,7 +12,13 @@ public class WindowsSetFileAssociationsTests [TestMethod] public void EnsureAssociationsSet() { - var filePath = new CreateFakeStarskyExe().FullFilePath; + if ( !new AppSettings().IsWindows ) + { + Assert.Inconclusive("This test if for Windows Only"); + return; + } + + var filePath = new CreateFakeStarskyWindowsExe().FullFilePath; WindowsSetFileAssociations.EnsureAssociationsSet( new FileAssociation { @@ -19,6 +27,13 @@ public void EnsureAssociationsSet() FileTypeDescription = "Starsky Test File", ExecutableFilePath = filePath }); + + var registryKeyPath = @"Software\Classes\starskytest\shell\open\command"; + + using ( RegistryKey key = Registry.LocalMachine.OpenSubKey(registryKeyPath) ) + { + var value = key.GetValue(string.Empty).ToString(); + } } } } diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs index 1a2577ca4b..1d147510a3 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs @@ -7,7 +7,6 @@ namespace starskytest.starsky.foundation.native.OpenApplicationNative { - [TestClass] public class OpenApplicationNativeServiceTest { @@ -17,7 +16,7 @@ public class OpenApplicationNativeServiceTest [TestInitialize] [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", - "CA1416:Validate platform compatibility", Justification = "Check does exists")] + "CA1416:Validate platform compatibility", Justification = "Check does exists")] public void TestInitialize() { if ( !new AppSettings().IsWindows ) @@ -54,7 +53,7 @@ public void OpenDefault_HappyFlow__WindowsOnly() return; } - var mock = new CreateFakeStarskyExe(); + var mock = new CreateFakeStarskyWindowsExe(); var filePath = mock.FullFilePath; WindowsSetFileAssociations.EnsureAssociationsSet( new FileAssociation @@ -65,7 +64,8 @@ public void OpenDefault_HappyFlow__WindowsOnly() ExecutableFilePath = filePath }); - var result = new OpenApplicationNativeService().OpenDefault([mock.StarskyDotStarskyPath]); + var result = + new OpenApplicationNativeService().OpenDefault([mock.StarskyDotStarskyPath]); Assert.IsTrue(result); } @@ -77,7 +77,6 @@ public void OpenApplicationAtUrl_ZeroItems_SoFalse() Assert.IsFalse(result); } - [TestMethod] public void OpenDefault_ZeroItemsSo_False() { diff --git a/starsky/starskytest/starskytest.csproj b/starsky/starskytest/starskytest.csproj index 442d4e6cd4..c0296373b1 100644 --- a/starsky/starskytest/starskytest.csproj +++ b/starsky/starskytest/starskytest.csproj @@ -17,16 +17,16 @@ $(RootFolder)\build.vstest.runsettings - - + + - - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -34,92 +34,96 @@ - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + - - - + + + - - - - - - - - - + + + + + + + + + PreserveNewest - + - + build.vstest.runsettings - + PreserveNewest - - + + PreserveNewest - PreserveNewest + PreserveNewest - PreserveNewest + PreserveNewest + + + + PreserveNewest From 5ebb51007bbb6d4d1d1a866f7219fd5cc351e27f Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 14 Feb 2024 20:15:22 +0100 Subject: [PATCH 016/125] fix test issues --- .../Helpers/WindowsOpenDesktopAppTests.cs | 35 ++++++++---- .../WindowsSetFileAssociationsTests.cs | 54 ++++++++++++++++--- .../OpenApplicationNativeServiceTest.cs | 45 +++++++++------- 3 files changed, 96 insertions(+), 38 deletions(-) diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs index 411998b36a..79376456f0 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs @@ -20,7 +20,7 @@ public class WindowsOpenDesktopAppTests [TestInitialize] public void TestInitialize() { - CleanSetup(); + SetupEnsureAssociationsSet(); } [TestCleanup] @@ -43,6 +43,26 @@ private static void CleanSetup() Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{ProgId}", false); } + private static CreateFakeStarskyWindowsExe SetupEnsureAssociationsSet() + { + if ( !new AppSettings().IsWindows ) + { + return new CreateFakeStarskyWindowsExe(); + } + + var mock = new CreateFakeStarskyWindowsExe(); + var filePath = mock.FullFilePath; + WindowsSetFileAssociations.EnsureAssociationsSet( + new FileAssociation + { + Extension = Extension, + ProgId = ProgId, + FileTypeDescription = FileTypeDescription, + ExecutableFilePath = filePath + }); + return mock; + } + [TestMethod] public void W_OpenDefault_NonWindows() @@ -52,7 +72,7 @@ public void W_OpenDefault_NonWindows() } [TestMethod] - public void W_OpenDefault_HappyFlow__WindowsOnly() + public async Task W_OpenDefault_HappyFlow__WindowsOnly() { if ( !new AppSettings().IsWindows ) { @@ -61,15 +81,8 @@ public void W_OpenDefault_HappyFlow__WindowsOnly() } var mock = new CreateFakeStarskyWindowsExe(); - var filePath = mock.FullFilePath; - WindowsSetFileAssociations.EnsureAssociationsSet( - new FileAssociation - { - Extension = Extension, - ProgId = ProgId, - FileTypeDescription = FileTypeDescription, - ExecutableFilePath = filePath - }); + + await Task.Delay(50); var result = WindowsOpenDesktopApp.OpenDefault([mock.StarskyDotStarskyPath], OSPlatform.Windows); diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs index 5cfd9e77d2..f138ea69ac 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs @@ -3,13 +3,46 @@ using starsky.foundation.native.OpenApplicationNative.Helpers; using starsky.foundation.platform.Models; using starskytest.FakeCreateAn.CreateFakeStarskyExe; +using System.Text.RegularExpressions; namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers { [TestClass] public class WindowsSetFileAssociationsTests { + private const string Extension = ".starsky"; + private const string ProgId = "starskytest"; + private const string FileTypeDescription = "Starsky Test File"; + + [TestInitialize] + public void TestInitialize() + { + CleanSetup(); + } + + [TestCleanup] + public void TestCleanup() + { + CleanSetup(); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", + "CA1416:Validate platform compatibility", Justification = "Check does exists")] + private static void CleanSetup() + { + if ( !new AppSettings().IsWindows ) + { + return; + } + + // Ensure no keys exist before the test starts + Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{Extension}", false); + Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{ProgId}", false); + } + [TestMethod] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", + "CA1416:Validate platform compatibility", Justification = "Check if test for windows only")] public void EnsureAssociationsSet() { if ( !new AppSettings().IsWindows ) @@ -22,18 +55,23 @@ public void EnsureAssociationsSet() WindowsSetFileAssociations.EnsureAssociationsSet( new FileAssociation { - Extension = ".starsky", - ProgId = "starskytest", - FileTypeDescription = "Starsky Test File", + Extension = Extension, + ProgId = ProgId, + FileTypeDescription = FileTypeDescription, ExecutableFilePath = filePath }); - var registryKeyPath = @"Software\Classes\starskytest\shell\open\command"; + var registryKeyPath = $@"Software\Classes\{ProgId}\shell\open\command"; - using ( RegistryKey key = Registry.LocalMachine.OpenSubKey(registryKeyPath) ) - { - var value = key.GetValue(string.Empty).ToString(); - } + using var key = Registry.CurrentUser.OpenSubKey(registryKeyPath); + + var valueKey = key?.GetValue(string.Empty)?.ToString(); + var pattern = "\"([^\"]*)\""; + Assert.IsNotNull( valueKey ); + var match = Regex.Match(valueKey, pattern); + var value = match.Groups[1].Value; + + Assert.AreEqual(filePath, value); } } } diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs index 1d147510a3..82fd4da042 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs @@ -15,37 +15,53 @@ public class OpenApplicationNativeServiceTest private const string FileTypeDescription = "Starsky Test File"; [TestInitialize] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", - "CA1416:Validate platform compatibility", Justification = "Check does exists")] public void TestInitialize() + { + SetupEnsureAssociationsSet(); + } + + private static CreateFakeStarskyWindowsExe SetupEnsureAssociationsSet() { if ( !new AppSettings().IsWindows ) { - return; + return new CreateFakeStarskyWindowsExe(); } - // Ensure no keys exist before the test starts - Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{Extension}", false); - Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{ProgId}", false); + var mock = new CreateFakeStarskyWindowsExe(); + var filePath = mock.FullFilePath; + WindowsSetFileAssociations.EnsureAssociationsSet( + new FileAssociation + { + Extension = Extension, + ProgId = ProgId, + FileTypeDescription = FileTypeDescription, + ExecutableFilePath = filePath + }); + return mock; } [TestCleanup] + public void TestCleanup() + { + CleanSetup(); + } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Check does exists")] - public void TestCleanup() + private static void CleanSetup() { if ( !new AppSettings().IsWindows ) { return; } - // Cleanup created keys + // Ensure no keys exist before the test starts Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{Extension}", false); Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{ProgId}", false); } [TestMethod] - public void OpenDefault_HappyFlow__WindowsOnly() + public void Service_OpenDefault_HappyFlow__WindowsOnly() { if ( !new AppSettings().IsWindows ) { @@ -53,16 +69,7 @@ public void OpenDefault_HappyFlow__WindowsOnly() return; } - var mock = new CreateFakeStarskyWindowsExe(); - var filePath = mock.FullFilePath; - WindowsSetFileAssociations.EnsureAssociationsSet( - new FileAssociation - { - Extension = Extension, - ProgId = ProgId, - FileTypeDescription = FileTypeDescription, - ExecutableFilePath = filePath - }); + var mock = SetupEnsureAssociationsSet(); var result = new OpenApplicationNativeService().OpenDefault([mock.StarskyDotStarskyPath]); From 98576579c005e5761fae988f0837b47ef9348720 Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 14 Feb 2024 20:19:39 +0100 Subject: [PATCH 017/125] Give regex more time --- .../TestForNonAuthorizeAttributesTest.cs | 150 ++++++++++-------- 1 file changed, 84 insertions(+), 66 deletions(-) diff --git a/starsky/starskytest/AuthorizeAttributeTest/TestForNonAuthorizeAttributesTest.cs b/starsky/starskytest/AuthorizeAttributeTest/TestForNonAuthorizeAttributesTest.cs index c52f306263..f9eba86016 100644 --- a/starsky/starskytest/AuthorizeAttributeTest/TestForNonAuthorizeAttributesTest.cs +++ b/starsky/starskytest/AuthorizeAttributeTest/TestForNonAuthorizeAttributesTest.cs @@ -14,78 +14,96 @@ namespace starskytest.AuthorizeAttributeTest; [TestClass] public class TestForNonAuthorizeAttributesTest { - private static Type[] GetControllersInNamespace(Assembly assembly, string controllerNamespace) - { - return assembly.GetTypes().Where(types => string.Equals(types.Namespace, controllerNamespace, StringComparison.Ordinal)).ToArray(); - } + private static Type[] GetControllersInNamespace(Assembly assembly, string controllerNamespace) + { + return assembly.GetTypes().Where(types => + string.Equals(types.Namespace, controllerNamespace, StringComparison.Ordinal)) + .ToArray(); + } - private static (Assembly, string) GetAssembly() - { - var getEverythingBeforeLastDotRegex = new Regex(".*(?=\\.)", RegexOptions.None, TimeSpan.FromMilliseconds(100)); - var fullName = typeof(T).FullName; - if (string.IsNullOrEmpty(fullName)) - { - throw new Exception("Type does not have a FullName"); - } - var name = getEverythingBeforeLastDotRegex.Match(fullName).Value; - var assembly = typeof(T).Assembly; - return (assembly, name); - } + private static (Assembly, string) GetAssembly() + { + var getEverythingBeforeLastDotRegex = new Regex(".*(?=\\.)", RegexOptions.None, + TimeSpan.FromMilliseconds(200)); - [SuppressMessage("Usage", "S6602:\"Find\" method should be used instead of the \"FirstOrDefault\" extension")] - private static List GetControllerMethods(Type projectController) - { - var projectMethods = projectController.GetMethods(BindingFlags.Public | BindingFlags.Instance); + var fullName = typeof(T).FullName; + if ( string.IsNullOrEmpty(fullName) ) + { + throw new Exception("Type does not have a FullName"); + } - var (controllerBaseAssembly, controllerBaseName) = GetAssembly(); - var controllersBaseInNamespace = GetControllersInNamespace(controllerBaseAssembly, controllerBaseName); - var controllersBaseInNamespaceMethods = controllersBaseInNamespace.SelectMany(p => p.GetMethods()).ToArray(); - - var (controllerGenericTypeAssembly, controllerGenericTypeName) = GetAssembly(); - var controllersGenericTypeInNamespace = GetControllersInNamespace(controllerGenericTypeAssembly, controllerGenericTypeName); - var controllersGenericTypeNamespaceMethods = controllersGenericTypeInNamespace.SelectMany(p => p.GetMethods()).ToArray(); - - var allGenericControllerTypes = controllersBaseInNamespaceMethods.Concat(controllersGenericTypeNamespaceMethods).ToArray(); + var name = getEverythingBeforeLastDotRegex.Match(fullName).Value; + var assembly = typeof(T).Assembly; + return ( assembly, name ); + } - var projectsThatNotInheritFromControllerBase = new List(); - // ReSharper disable once LoopCanBeConvertedToQuery - foreach (var projectMethod in projectMethods) - { - var match = allGenericControllerTypes.FirstOrDefault(p => p.Name == projectMethod.Name); - if (match == null) - { - projectsThatNotInheritFromControllerBase.Add(projectMethod); - } - - } - - return projectsThatNotInheritFromControllerBase; - } + [SuppressMessage("Usage", + "S6602:\"Find\" method should be used instead of the \"FirstOrDefault\" extension")] + private static List GetControllerMethods(Type projectController) + { + var projectMethods = + projectController.GetMethods(BindingFlags.Public | BindingFlags.Instance); - [TestMethod] - public void TestForNonAuthorizeAttributes() - { - var (assembly, name) = GetAssembly(); - var controllersInNamespace = GetControllersInNamespace(assembly, name); + var (controllerBaseAssembly, controllerBaseName) = GetAssembly(); + var controllersBaseInNamespace = + GetControllersInNamespace(controllerBaseAssembly, controllerBaseName); + var controllersBaseInNamespaceMethods = + controllersBaseInNamespace.SelectMany(p => p.GetMethods()).ToArray(); - foreach (var controller in controllersInNamespace) - { - var methods = GetControllerMethods(controller); - foreach (var method in methods) - { - var authorizeAttributes = method.GetCustomAttributes(typeof(AuthorizeAttribute), true); - var authorizeParentAttributes = method.DeclaringType?.GetCustomAttributes(typeof(AuthorizeAttribute), true) ?? Array.Empty(); - var allowAnonymousAttributes = method.GetCustomAttributes(typeof(AllowAnonymousAttribute), true); - var allowAnonymousParentAttributes = method.DeclaringType?.GetCustomAttributes(typeof(AllowAnonymousAttribute), true) ?? Array.Empty(); + var (controllerGenericTypeAssembly, controllerGenericTypeName) = GetAssembly(); + var controllersGenericTypeInNamespace = + GetControllersInNamespace(controllerGenericTypeAssembly, controllerGenericTypeName); + var controllersGenericTypeNamespaceMethods = controllersGenericTypeInNamespace + .SelectMany(p => p.GetMethods()).ToArray(); - var attributes = new List(); - attributes.AddRange(authorizeAttributes); - attributes.AddRange(authorizeParentAttributes); - attributes.AddRange(allowAnonymousAttributes); - attributes.AddRange(allowAnonymousParentAttributes); + var allGenericControllerTypes = controllersBaseInNamespaceMethods + .Concat(controllersGenericTypeNamespaceMethods).ToArray(); - Assert.IsTrue(attributes.Count != 0, $"No AuthorizeAttribute found on {controller.FullName} {method.Name} method"); - } - } - } + var projectsThatNotInheritFromControllerBase = new List(); + // ReSharper disable once LoopCanBeConvertedToQuery + foreach ( var projectMethod in projectMethods ) + { + var match = allGenericControllerTypes.FirstOrDefault(p => p.Name == projectMethod.Name); + if ( match == null ) + { + projectsThatNotInheritFromControllerBase.Add(projectMethod); + } + } + + return projectsThatNotInheritFromControllerBase; + } + + [TestMethod] + public void TestForNonAuthorizeAttributes() + { + var (assembly, name) = GetAssembly(); + var controllersInNamespace = GetControllersInNamespace(assembly, name); + + foreach ( var controller in controllersInNamespace ) + { + var methods = GetControllerMethods(controller); + foreach ( var method in methods ) + { + var authorizeAttributes = + method.GetCustomAttributes(typeof(AuthorizeAttribute), true); + var authorizeParentAttributes = + method.DeclaringType?.GetCustomAttributes(typeof(AuthorizeAttribute), true) ?? + Array.Empty(); + var allowAnonymousAttributes = + method.GetCustomAttributes(typeof(AllowAnonymousAttribute), true); + var allowAnonymousParentAttributes = + method.DeclaringType?.GetCustomAttributes(typeof(AllowAnonymousAttribute), + true) ?? Array.Empty(); + + var attributes = new List(); + attributes.AddRange(authorizeAttributes); + attributes.AddRange(authorizeParentAttributes); + attributes.AddRange(allowAnonymousAttributes); + attributes.AddRange(allowAnonymousParentAttributes); + + Assert.IsTrue(attributes.Count != 0, + $"No AuthorizeAttribute found on {controller.FullName} {method.Name} method"); + } + } + } } From e737df871fa8cc4ef0ca2cdf3d8e68cd5d302eda Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 14 Feb 2024 20:38:36 +0100 Subject: [PATCH 018/125] supress warnings && add files back --- .../DisabledWebSocketsMiddleware.cs | 3 ++ .../starsky/Controllers/AccountController.cs | 29 ++++++++++-------- starsky/starsky/Controllers/HomeController.cs | 1 + .../Controllers/ThumbnailController.cs | 2 ++ starsky/starsky/Startup.cs | 21 ++++++------- .../MacOsStub.cpp | 0 .../MacOsStub.dll | Bin .../MacOsStub.h | 0 .../readme.md | 0 .../starskytest/FakeMocks/FakeIImportQuery.cs | 29 ++++++++++-------- starsky/starskytest/starskytest.csproj | 22 ++++++++----- 11 files changed, 65 insertions(+), 42 deletions(-) rename starsky/starskytest/FakeCreateAn/{CreateOpenApplicationNative => CreateOpenApplicationNative.reference}/MacOsStub.cpp (100%) rename starsky/starskytest/FakeCreateAn/{CreateOpenApplicationNative => CreateOpenApplicationNative.reference}/MacOsStub.dll (100%) rename starsky/starskytest/FakeCreateAn/{CreateOpenApplicationNative => CreateOpenApplicationNative.reference}/MacOsStub.h (100%) rename starsky/starskytest/FakeCreateAn/{CreateOpenApplicationNative => CreateOpenApplicationNative.reference}/readme.md (100%) diff --git a/starsky/starsky.foundation.realtime/Middleware/DisabledWebSocketsMiddleware.cs b/starsky/starsky.foundation.realtime/Middleware/DisabledWebSocketsMiddleware.cs index caa5a788f1..545229d89a 100644 --- a/starsky/starsky.foundation.realtime/Middleware/DisabledWebSocketsMiddleware.cs +++ b/starsky/starsky.foundation.realtime/Middleware/DisabledWebSocketsMiddleware.cs @@ -9,6 +9,8 @@ namespace starsky.foundation.realtime.Middleware [SuppressMessage("Performance", "CA1822:Mark members as static")] public sealed class DisabledWebSocketsMiddleware { + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + [SuppressMessage("Usage", "IDE0060:Remove unused parameter")] public DisabledWebSocketsMiddleware(RequestDelegate next) { } @@ -23,6 +25,7 @@ await webSocket.CloseOutputAsync(WebSocketCloseStatus.MessageTooBig, "Feature toggle disabled", CancellationToken.None); return; } + context.Response.StatusCode = StatusCodes.Status400BadRequest; } } diff --git a/starsky/starsky/Controllers/AccountController.cs b/starsky/starsky/Controllers/AccountController.cs index c5e26c60c2..fcf8c8e262 100644 --- a/starsky/starsky/Controllers/AccountController.cs +++ b/starsky/starsky/Controllers/AccountController.cs @@ -23,12 +23,14 @@ public sealed class AccountController : Controller private readonly IAntiforgery _antiForgery; private readonly IStorage _storageHostFullPathFilesystem; - public AccountController(IUserManager userManager, AppSettings appSettings, IAntiforgery antiForgery, ISelectorStorage selectorStorage) + public AccountController(IUserManager userManager, AppSettings appSettings, + IAntiforgery antiForgery, ISelectorStorage selectorStorage) { _userManager = userManager; _appSettings = appSettings; _antiForgery = antiForgery; - _storageHostFullPathFilesystem = selectorStorage.Get(SelectorStorage.StorageServices.HostFilesystem); + _storageHostFullPathFilesystem = + selectorStorage.Get(SelectorStorage.StorageServices.HostFilesystem); } /// @@ -76,9 +78,7 @@ public async Task Status() var model = new UserIdentifierStatusModel { - Name = currentUser.Name, - Id = currentUser.Id, - Created = currentUser.Created, + Name = currentUser.Name, Id = currentUser.Id, Created = currentUser.Created, }; var credentials = _userManager.GetCredentialsByUserId(currentUser.Id); @@ -105,6 +105,7 @@ public async Task Status() [ProducesResponseType(200)] [Produces("text/html")] [SuppressMessage("ReSharper", "UnusedParameter.Global")] + [SuppressMessage("Usage", "IDE0060:Remove unused parameter")] [AllowAnonymous] public IActionResult LoginGet(string? returnUrl = null, bool? fromLogout = null) { @@ -112,7 +113,8 @@ public IActionResult LoginGet(string? returnUrl = null, bool? fromLogout = null) var clientApp = Path.Combine(_appSettings.BaseDirectoryProject, "clientapp", "build", "index.html"); - if ( !_storageHostFullPathFilesystem.ExistFile(clientApp) ) return Content("Please check if the client code exist"); + if ( !_storageHostFullPathFilesystem.ExistFile(clientApp) ) + return Content("Please check if the client code exist"); return PhysicalFile(clientApp, "text/html"); } @@ -137,7 +139,8 @@ public IActionResult LoginGet(string? returnUrl = null, bool? fromLogout = null) [AllowAnonymous] public async Task LoginPost(LoginViewModel model) { - ValidateResult validateResult = await _userManager.ValidateAsync("Email", model.Email, model.Password); + ValidateResult validateResult = + await _userManager.ValidateAsync("Email", model.Email, model.Password); if ( !validateResult.Success ) { @@ -146,6 +149,7 @@ public async Task LoginPost(LoginViewModel model) { Response.StatusCode = 423; } + return Json("Login failed"); } @@ -186,7 +190,8 @@ public IActionResult Logout(string? returnUrl = null) { _userManager.SignOut(HttpContext); // fromLogout is used in middleware - return RedirectToAction(nameof(LoginGet), new { ReturnUrl = returnUrl, fromLogout = true }); + return RedirectToAction(nameof(LoginGet), + new { ReturnUrl = returnUrl, fromLogout = true }); } /// @@ -211,7 +216,7 @@ public async Task ChangeSecret(ChangePasswordViewModel model) } if ( !ModelState.IsValid || - model.ChangedPassword != model.ChangedConfirmPassword ) + model.ChangedPassword != model.ChangedConfirmPassword ) { return BadRequest("Model is not correct"); } @@ -282,7 +287,8 @@ public async Task Register(RegisterViewModel model) private async Task IsAccountRegisterClosed(bool userIdentityIsAuthenticated) { if ( userIdentityIsAuthenticated ) return false; - return _appSettings.IsAccountRegisterOpen != true && ( await _userManager.AllUsersAsync() ).Users.Count != 0; + return _appSettings.IsAccountRegisterOpen != true && + ( await _userManager.AllUsersAsync() ).Users.Count != 0; } /// @@ -305,7 +311,7 @@ public async Task RegisterStatus() } if ( !await IsAccountRegisterClosed( - User.Identity?.IsAuthenticated == true) ) + User.Identity?.IsAuthenticated == true) ) { return Json("RegisterStatus open"); } @@ -329,6 +335,5 @@ public IActionResult Permissions() var claims = User.Claims.Where(p => p.Type == "Permission").Select(p => p.Value); return Json(claims); } - } } diff --git a/starsky/starsky/Controllers/HomeController.cs b/starsky/starsky/Controllers/HomeController.cs index 9d77106361..1bb115b407 100644 --- a/starsky/starsky/Controllers/HomeController.cs +++ b/starsky/starsky/Controllers/HomeController.cs @@ -35,6 +35,7 @@ public HomeController(AppSettings appSettings, IAntiforgery antiForgery) [Produces("text/html")] [ProducesResponseType(200)] [ProducesResponseType(401)] + [SuppressMessage("Usage", "IDE0060:Remove unused parameter")] public IActionResult Index(string f = "") { new AntiForgeryCookie(_antiForgery).SetAntiForgeryCookie(HttpContext); diff --git a/starsky/starsky/Controllers/ThumbnailController.cs b/starsky/starsky/Controllers/ThumbnailController.cs index 45807b871d..32332f26a7 100644 --- a/starsky/starsky/Controllers/ThumbnailController.cs +++ b/starsky/starsky/Controllers/ThumbnailController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; @@ -303,6 +304,7 @@ public async Task Thumbnail( [ProducesResponseType(400)] // string (f) input not allowed to avoid path injection attacks [ProducesResponseType(404)] // not found [ProducesResponseType(210)] // raw + [SuppressMessage("Usage", "IDE0060:Remove unused parameter")] public async Task ByZoomFactorAsync( string f, int z = 0, diff --git a/starsky/starsky/Startup.cs b/starsky/starsky/Startup.cs index a7dc9f69cd..3d3be39fa1 100644 --- a/starsky/starsky/Startup.cs +++ b/starsky/starsky/Startup.cs @@ -57,7 +57,7 @@ public Startup(string[]? args = null) if ( !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("app__appsettingspath")) ) { Console.WriteLine("app__appSettingsPath: " + - Environment.GetEnvironmentVariable("app__appsettingspath")); + Environment.GetEnvironmentVariable("app__appsettingspath")); } _configuration = SetupAppSettings.AppSettingsToBuilder(args).ConfigureAwait(false) @@ -257,8 +257,7 @@ private static Func, Task> ReplaceR /// ApplicationBuilder /// Hosting Env /// application Lifetime - public void Configure(IApplicationBuilder app, IHostEnvironment env, - IHostApplicationLifetime applicationLifetime) + public void Configure(IApplicationBuilder app, IHostEnvironment env) { app.UseResponseCompression(); @@ -289,7 +288,7 @@ public void Configure(IApplicationBuilder app, IHostEnvironment env, app.UseAuthentication(); app.UseBasicAuthentication(); app.UseNoAccount(_appSettings?.NoAccountLocalhost == true || - _appSettings?.DemoUnsafeDeleteStorageFolder == true); + _appSettings?.DemoUnsafeDeleteStorageFolder == true); app.UseCheckIfAccountExist(); app.UseAuthorization(); @@ -322,7 +321,7 @@ public void Configure(IApplicationBuilder app, IHostEnvironment env, internal (bool, bool, bool) SetupStaticFiles(IApplicationBuilder app, string assetsName = "assets") { - var result = (false, false, false); + var result = ( false, false, false ); // Allow Current Directory and wwwroot in Base Directory // AppSettings can be null when running tests @@ -351,19 +350,19 @@ public void Configure(IApplicationBuilder app, IHostEnvironment env, // Check if clientapp is build and use the assets folder if ( !Directory.Exists(Path.Combine( - _appSettings.BaseDirectoryProject, "clientapp", "build", assetsName)) ) + _appSettings.BaseDirectoryProject, "clientapp", "build", assetsName)) ) { return result; } app.UseStaticFiles(new StaticFileOptions - { - OnPrepareResponse = PrepareResponse, - FileProvider = new PhysicalFileProvider( + { + OnPrepareResponse = PrepareResponse, + FileProvider = new PhysicalFileProvider( Path.Combine(_appSettings.BaseDirectoryProject, "clientapp", "build", assetsName)), - RequestPath = $"/assets", - } + RequestPath = $"/assets", + } ); result.Item3 = true; return result; diff --git a/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/MacOsStub.cpp b/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/MacOsStub.cpp similarity index 100% rename from starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/MacOsStub.cpp rename to starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/MacOsStub.cpp diff --git a/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/MacOsStub.dll b/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/MacOsStub.dll similarity index 100% rename from starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/MacOsStub.dll rename to starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/MacOsStub.dll diff --git a/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/MacOsStub.h b/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/MacOsStub.h similarity index 100% rename from starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/MacOsStub.h rename to starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/MacOsStub.h diff --git a/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/readme.md b/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/readme.md similarity index 100% rename from starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative/readme.md rename to starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/readme.md diff --git a/starsky/starskytest/FakeMocks/FakeIImportQuery.cs b/starsky/starskytest/FakeMocks/FakeIImportQuery.cs index a845aa5253..8369b5e5cc 100644 --- a/starsky/starskytest/FakeMocks/FakeIImportQuery.cs +++ b/starsky/starskytest/FakeMocks/FakeIImportQuery.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using starsky.foundation.database.Data; @@ -15,11 +16,11 @@ public class FakeIImportQuery : IImportQuery private readonly List _exist; private readonly bool _isConnection; - public FakeIImportQuery(List exist, bool isConnection = true) + public FakeIImportQuery(List? exist, bool isConnection = true) { - _exist = exist; + _exist = exist!; _isConnection = isConnection; - if ( exist == null ) _exist = new List(); + if ( exist == null ) _exist = []; } public FakeIImportQuery() @@ -35,12 +36,16 @@ public FakeIImportQuery() /// /// /// - public FakeIImportQuery(IServiceScopeFactory scopeFactory, IConsole console, IWebLogger logger, ApplicationDbContext? dbContext = null) + [SuppressMessage("ReSharper", "UnusedParameter.Local", + Justification = "Auto Inject")] + [SuppressMessage("Usage", "IDE0060:Remove unused parameter")] + public FakeIImportQuery(IServiceScopeFactory scopeFactory, IConsole console, + IWebLogger logger, ApplicationDbContext? dbContext = null) { _exist = new List(); _isConnection = true; } - + public async Task IsHashInImportDbAsync(string fileHashCode) { return _exist.Contains(fileHashCode); @@ -51,7 +56,8 @@ public bool TestConnection() return _isConnection; } - public async Task AddAsync(ImportIndexItem updateStatusContent, bool writeConsole = true) + public async Task AddAsync(ImportIndexItem updateStatusContent, + bool writeConsole = true) { _exist.Add(updateStatusContent.FileHash!); return true; @@ -62,21 +68,20 @@ public List History() var newFakeList = new List(); foreach ( var exist in _exist ) { - newFakeList.Add(new ImportIndexItem - { - Status = ImportStatus.Ok, - FilePath = exist - }); + newFakeList.Add(new ImportIndexItem { Status = ImportStatus.Ok, FilePath = exist }); } + return newFakeList; } - public async Task> AddRangeAsync(List importIndexItemList) + public async Task> AddRangeAsync( + List importIndexItemList) { foreach ( var importIndexItem in importIndexItemList ) { await AddAsync(importIndexItem); } + return importIndexItemList; } diff --git a/starsky/starskytest/starskytest.csproj b/starsky/starskytest/starskytest.csproj index 9d6f1e90b4..5f6e816c3e 100644 --- a/starsky/starskytest/starskytest.csproj +++ b/starsky/starskytest/starskytest.csproj @@ -20,13 +20,13 @@ - - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -113,6 +113,14 @@ PreserveNewest + + + PreserveNewest + + + + PreserveNewest + From 7b21b2b5cf60678163df6b3ffd0f52bb3a9bb5bd Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 14 Feb 2024 20:39:47 +0100 Subject: [PATCH 019/125] fix test --- starsky/starsky/Program.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/starsky/starsky/Program.cs b/starsky/starsky/Program.cs index 98863a6f10..8f688f4453 100644 --- a/starsky/starsky/Program.cs +++ b/starsky/starsky/Program.cs @@ -29,8 +29,7 @@ public static async Task Main(string[] args) builder.Host.UseWindowsService(); var app = builder.Build(); - var hostLifetime = app.Services.GetRequiredService(); - startup.Configure(app, builder.Environment, hostLifetime); + startup.Configure(app, builder.Environment); await RunAsync(app, args.All(p => p != "--do-not-start")); } From bbca16eada865c6d18b31c46e10fd845f34ad663 Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 14 Feb 2024 20:42:04 +0100 Subject: [PATCH 020/125] fixing stuff --- starsky/starsky/Controllers/HomeController.cs | 18 ++- starsky/starsky/Program.cs | 1 - starsky/starsky/Startup.cs | 1 - starsky/starskytest/root/StartupTest.cs | 116 ++++++++++-------- 4 files changed, 79 insertions(+), 57 deletions(-) diff --git a/starsky/starsky/Controllers/HomeController.cs b/starsky/starsky/Controllers/HomeController.cs index 1bb115b407..2b14477c3a 100644 --- a/starsky/starsky/Controllers/HomeController.cs +++ b/starsky/starsky/Controllers/HomeController.cs @@ -10,6 +10,7 @@ using starsky.Helpers; [assembly: InternalsVisibleTo("starskytest")] + namespace starsky.Controllers { [Authorize] @@ -64,10 +65,11 @@ public IActionResult SearchPost(string t = "", int p = 0) // Added filter to prevent redirects based on tainted, user-controlled data // unescaped: ^[a-zA-Z0-9_\-+"'/=:,\.>< ]+$ if ( !Regex.IsMatch(t, "^[a-zA-Z0-9_\\-+\"'/=:,\\.>< ]+$", - RegexOptions.None, TimeSpan.FromMilliseconds(100)) ) + RegexOptions.None, TimeSpan.FromMilliseconds(100)) ) { return BadRequest("`t` is not allowed"); } + return Redirect(AppendPathBasePrefix(Request.PathBase.Value, $"/search?t={t}&p={p}")); } @@ -103,10 +105,11 @@ public IActionResult Search(string t = "", int p = 0) // Added filter to prevent redirects based on tainted, user-controlled data // unescaped: ^[a-zA-Z0-9_\-+"'/=:>< ]+$ if ( !Regex.IsMatch(t, "^[a-zA-Z0-9_\\-+\"'/=:>< ]+$", - RegexOptions.None, TimeSpan.FromMilliseconds(100)) ) + RegexOptions.None, TimeSpan.FromMilliseconds(100)) ) { return BadRequest("`t` is not allowed"); } + return Redirect(AppendPathBasePrefix(Request.PathBase.Value, $"/search?t={t}&p={p}")); } @@ -129,6 +132,7 @@ public IActionResult Trash(int p = 0) { return Redirect(AppendPathBasePrefix(Request.PathBase.Value, $"/trash?p={p}")); } + return PhysicalFile(_clientApp, "text/html"); } @@ -148,6 +152,7 @@ public IActionResult Import() { return Redirect(AppendPathBasePrefix(Request.PathBase.Value, $"/import")); } + return PhysicalFile(_clientApp, "text/html"); } @@ -167,6 +172,7 @@ public IActionResult Preferences() { return Redirect(AppendPathBasePrefix(Request.PathBase.Value, $"/preferences")); } + return PhysicalFile(_clientApp, "text/html"); } @@ -182,6 +188,7 @@ public IActionResult Preferences() [Produces("text/html")] [ProducesResponseType(200)] [SuppressMessage("ReSharper", "UnusedParameter.Global")] + [SuppressMessage("Usage", "IDE0060:Remove unused parameter")] public IActionResult Register(string? returnUrl = null) { new AntiForgeryCookie(_antiForgery).SetAntiForgeryCookie(HttpContext); @@ -191,10 +198,13 @@ public IActionResult Register(string? returnUrl = null) internal static string AppendPathBasePrefix(string? requestPathBase, string url) { return requestPathBase?.Equals("/starsky", - StringComparison.InvariantCultureIgnoreCase) == true ? $"/starsky{url}" : url; + StringComparison.InvariantCultureIgnoreCase) == true + ? $"/starsky{url}" + : url; } - internal static bool IsCaseSensitiveRedirect(string? expectedRequestPath, string? requestPathValue) + internal static bool IsCaseSensitiveRedirect(string? expectedRequestPath, + string? requestPathValue) { return expectedRequestPath != requestPathValue; } diff --git a/starsky/starsky/Program.cs b/starsky/starsky/Program.cs index 8f688f4453..8ef97230e0 100644 --- a/starsky/starsky/Program.cs +++ b/starsky/starsky/Program.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Models; diff --git a/starsky/starsky/Startup.cs b/starsky/starsky/Startup.cs index 3d3be39fa1..52eb3e16ae 100644 --- a/starsky/starsky/Startup.cs +++ b/starsky/starsky/Startup.cs @@ -256,7 +256,6 @@ private static Func, Task> ReplaceR /// /// ApplicationBuilder /// Hosting Env - /// application Lifetime public void Configure(IApplicationBuilder app, IHostEnvironment env) { app.UseResponseCompression(); diff --git a/starsky/starskytest/root/StartupTest.cs b/starsky/starskytest/root/StartupTest.cs index 8e0f108233..62bfd483ac 100644 --- a/starsky/starskytest/root/StartupTest.cs +++ b/starsky/starskytest/root/StartupTest.cs @@ -30,23 +30,26 @@ public void Startup_ConfigureServices() { IServiceCollection serviceCollection = new ServiceCollection(); // needed for: AddMetrics - IConfiguration configuration = new ConfigurationRoot(new List()); - serviceCollection.AddSingleton(configuration); - + IConfiguration configuration = + new ConfigurationRoot(new List()); + serviceCollection.AddSingleton(configuration); + // should not crash new Startup().ConfigureServices(serviceCollection); Assert.IsNotNull(serviceCollection); } - + [TestMethod] public void Startup_ConfigureServicesConfigure1() { var serviceCollection = new ServiceCollection(); serviceCollection.AddRouting(); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - IConfiguration configuration = new ConfigurationRoot(new List()); - serviceCollection.AddSingleton(configuration); + serviceCollection + .AddSingleton(); + IConfiguration configuration = + new ConfigurationRoot(new List()); + serviceCollection.AddSingleton(configuration); serviceCollection.AddAuthorization(); serviceCollection.AddControllers(); serviceCollection.AddLogging(); @@ -54,19 +57,20 @@ public void Startup_ConfigureServicesConfigure1() var serviceProvider = serviceCollection.BuildServiceProvider(); var serviceProviderInterface = serviceProvider.GetRequiredService(); - + var applicationBuilder = new ApplicationBuilder(serviceProviderInterface); - IHostEnvironment env = new HostingEnvironment { EnvironmentName = Environments.Development }; + IHostEnvironment env = + new HostingEnvironment { EnvironmentName = Environments.Development }; // should not crash var startup = new Startup(); - + startup.ConfigureServices(serviceCollection); var appSettings = serviceProvider.GetRequiredService(); appSettings.UseRealtime = true; - startup.Configure(applicationBuilder, env, new FakeIApplicationLifetime()); - + startup.Configure(applicationBuilder, env); + Assert.IsNotNull(applicationBuilder); Assert.IsNotNull(env); } @@ -74,14 +78,15 @@ public void Startup_ConfigureServicesConfigure1() [SuppressMessage("ReSharper", "ReturnTypeCanBeEnumerable.Local")] private static List? GetMiddlewareInstance(IApplicationBuilder app) { - const string middlewareTypeName = "Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware"; + const string middlewareTypeName = + "Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware"; var appBuilderType = typeof(ApplicationBuilder); const BindingFlags bindingTypes1 = BindingFlags.Instance | - BindingFlags.NonPublic; + BindingFlags.NonPublic; var middlewareField = appBuilderType.GetField("_components", bindingTypes1); var components = middlewareField?.GetValue(app); - if (components != null) + if ( components != null ) { var element = components as List>; @@ -97,20 +102,21 @@ public void Startup_ConfigureServicesConfigure1() { var type = middleware.Target?.GetType(); const BindingFlags bindingTypes = BindingFlags.Instance | - BindingFlags.NonPublic | - BindingFlags.Public; + BindingFlags.NonPublic | + BindingFlags.Public; var privatePropertyInfo = type?.GetField("_args", bindingTypes); var privateFieldValue = privatePropertyInfo?.GetValue(middleware.Target) as object[]; - + status.Add(privateFieldValue); } + return status; } return null; } - + [TestMethod] public void BasicFlow_Default() { @@ -118,29 +124,32 @@ public void BasicFlow_Default() var serviceCollection = new ServiceCollection(); var serviceProvider = serviceCollection.BuildServiceProvider(); var serviceProviderInterface = serviceProvider.GetRequiredService(); - + var applicationBuilder = new ApplicationBuilder(serviceProviderInterface); var result = startup.SetupStaticFiles(applicationBuilder); - + Assert.IsNotNull(result); Assert.IsTrue(result.Item1); Assert.IsFalse(result.Item2); Assert.IsFalse(result.Item3); - - var middlewareInstance = GetMiddlewareInstance(applicationBuilder)?.FirstOrDefault() as object?[]; + + var middlewareInstance = + GetMiddlewareInstance(applicationBuilder)?.FirstOrDefault() as object?[]; var value = middlewareInstance?.FirstOrDefault() as OptionsWrapper; - + Assert.IsFalse(value?.Value.RequestPath.HasValue); Assert.AreEqual(string.Empty, value?.Value.RequestPath.Value); } - + [TestMethod] public void BasicFlow_Assets() { var storage = new StorageHostFullPathFilesystem(); - storage.CreateDirectory(Path.Combine(new AppSettings().BaseDirectoryProject, "wwwroot")); - storage.CreateDirectory(Path.Combine(new AppSettings().BaseDirectoryProject, "clientapp", "build", "assets")); - + storage.CreateDirectory(Path.Combine(new AppSettings().BaseDirectoryProject, + "wwwroot")); + storage.CreateDirectory(Path.Combine(new AppSettings().BaseDirectoryProject, + "clientapp", "build", "assets")); + var startup = new Startup(); var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(); @@ -154,19 +163,20 @@ public void BasicFlow_Assets() Assert.IsNotNull(result); Console.WriteLine("result:"); - Console.WriteLine("1: " +result.Item1 + " 2: " + result.Item2 + " 3: " + result.Item3); - + Console.WriteLine("1: " + result.Item1 + " 2: " + result.Item2 + " 3: " + result.Item3); + Assert.IsTrue(result.Item1); Assert.IsTrue(result.Item2); Assert.IsTrue(result.Item3); - var middlewareInstance = GetMiddlewareInstance(applicationBuilder)?.ToList()[1] as object?[]; + var middlewareInstance = + GetMiddlewareInstance(applicationBuilder)?.ToList()[1] as object?[]; var value = middlewareInstance?.FirstOrDefault() as OptionsWrapper; - + Assert.IsFalse(value?.Value.RequestPath.HasValue); Assert.AreEqual(string.Empty, value?.Value.RequestPath.Value); } - + [TestMethod] public void BasicFlow_Assets2() { @@ -175,31 +185,34 @@ public void BasicFlow_Assets2() serviceCollection.AddSingleton(); var serviceProvider = serviceCollection.BuildServiceProvider(); var serviceProviderInterface = serviceProvider.GetRequiredService(); - + var applicationBuilder = new ApplicationBuilder(serviceProviderInterface); startup.ConfigureServices(serviceCollection); - + var storage = new StorageHostFullPathFilesystem(); - storage.CreateDirectory(Path.Combine(new AppSettings().BaseDirectoryProject, "wwwroot")); - storage.CreateDirectory(Path.Combine(new AppSettings().BaseDirectoryProject, "clientapp", "build", "assets")); + storage.CreateDirectory(Path.Combine(new AppSettings().BaseDirectoryProject, + "wwwroot")); + storage.CreateDirectory(Path.Combine(new AppSettings().BaseDirectoryProject, + "clientapp", "build", "assets")); var result = startup.SetupStaticFiles(applicationBuilder); Assert.IsNotNull(result); - + Console.WriteLine("result:"); - Console.WriteLine("1: " +result.Item1 + " 2: " + result.Item2 + " 3: " + result.Item3); - + Console.WriteLine("1: " + result.Item1 + " 2: " + result.Item2 + " 3: " + result.Item3); + Assert.IsTrue(result.Item1); Assert.IsTrue(result.Item2); Assert.IsTrue(result.Item3); - var middlewareInstance = GetMiddlewareInstance(applicationBuilder)?.ToList()[2] as object?[]; + var middlewareInstance = + GetMiddlewareInstance(applicationBuilder)?.ToList()[2] as object?[]; var value = middlewareInstance?.FirstOrDefault() as OptionsWrapper; - + Assert.IsTrue(value?.Value.RequestPath.HasValue); Assert.AreEqual("/assets", value?.Value.RequestPath.Value); } - + [TestMethod] public void BasicFlow_Assets_NotFound() { @@ -208,19 +221,20 @@ public void BasicFlow_Assets_NotFound() serviceCollection.AddSingleton(); var serviceProvider = serviceCollection.BuildServiceProvider(); var serviceProviderInterface = serviceProvider.GetRequiredService(); - + var applicationBuilder = new ApplicationBuilder(serviceProviderInterface); startup.ConfigureServices(serviceCollection); - + var storage = new StorageHostFullPathFilesystem(); - storage.CreateDirectory(Path.Combine(new AppSettings().BaseDirectoryProject, "wwwroot")); + storage.CreateDirectory(Path.Combine(new AppSettings().BaseDirectoryProject, + "wwwroot")); - var result = startup.SetupStaticFiles(applicationBuilder,"not-found-folder-name"); + var result = startup.SetupStaticFiles(applicationBuilder, "not-found-folder-name"); Assert.IsNotNull(result); - + Console.WriteLine("result:"); - Console.WriteLine("1: " +result.Item1 + " 2: " + result.Item2 + " 3: " + result.Item3); - + Console.WriteLine("1: " + result.Item1 + " 2: " + result.Item2 + " 3: " + result.Item3); + Assert.IsTrue(result.Item1); Assert.IsTrue(result.Item2); Assert.IsFalse(result.Item3); @@ -236,7 +250,7 @@ public void PrepareResponse_CheckValues() Startup.PrepareResponse(context); // Assert Assert.IsNotNull(context.Context.Response.Headers.Expires); - Assert.AreEqual("public, max-age=31536000", + Assert.AreEqual("public, max-age=31536000", context.Context.Response.Headers.CacheControl.ToString()); } } From edcf7d46b339e780409b53bb41f2ed71a96b5357 Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 14 Feb 2024 21:56:51 +0100 Subject: [PATCH 021/125] add nighly --- starsky/nuget.config | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 starsky/nuget.config diff --git a/starsky/nuget.config b/starsky/nuget.config new file mode 100644 index 0000000000..7db60a6aae --- /dev/null +++ b/starsky/nuget.config @@ -0,0 +1,11 @@ + + + + + + + + + + + From 3eb5c6c04e912c4c20a4fedf250e3d76b9c45212 Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 14 Feb 2024 22:00:39 +0100 Subject: [PATCH 022/125] add check --- .../CreateFakeStarskyExe/CreateFakeStarskyUnixBash.cs | 5 +++++ .../CreateFakeStarskyExe/CreateFakeStarskyWindowsExe.cs | 5 +++++ starsky/starskytest/starskytest.csproj | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyUnixBash.cs b/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyUnixBash.cs index 196f5c7bff..3948b4c550 100644 --- a/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyUnixBash.cs +++ b/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyUnixBash.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.Reflection; @@ -14,6 +15,10 @@ public CreateFakeStarskyUnixBash() var path = Path.Combine(parentFolder, "starsky"); FullFilePath = path; StarskyDotStarskyPath = Path.Combine(parentFolder, "starsky.starsky"); + if ( !File.Exists(FullFilePath) || !File.Exists(StarskyDotStarskyPath) ) + { + throw new Exception("missing starsky or starsky.starsky file in " + parentFolder); + } } public string StarskyDotStarskyPath { get; set; } = string.Empty; diff --git a/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyWindowsExe.cs b/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyWindowsExe.cs index eb71700fc4..fe541fcf62 100644 --- a/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyWindowsExe.cs +++ b/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyWindowsExe.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.Reflection; @@ -14,6 +15,10 @@ public CreateFakeStarskyWindowsExe() var path = Path.Combine(parentFolder, "starsky.exe"); FullFilePath = path; StarskyDotStarskyPath = Path.Combine(parentFolder, "starsky.starsky"); + if ( !File.Exists(FullFilePath) || !File.Exists(StarskyDotStarskyPath) ) + { + throw new Exception("missing starsky.exe or starsky.starsky file in " + parentFolder); + } } public string FullFilePath { get; set; } = string.Empty; diff --git a/starsky/starskytest/starskytest.csproj b/starsky/starskytest/starskytest.csproj index 5f6e816c3e..db8fe10b4e 100644 --- a/starsky/starskytest/starskytest.csproj +++ b/starsky/starskytest/starskytest.csproj @@ -121,6 +121,10 @@ PreserveNewest + + + PreserveNewest + From 48b29812052bea4e99cd20231f25d19506ce1811 Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 14 Feb 2024 22:06:50 +0100 Subject: [PATCH 023/125] add debug --- .../OpenApplicationNative/OpenApplicationNativeServiceTest.cs | 3 +++ .../Trash/Helpers/WindowsShellTrashBindingHelperTest.cs | 1 + 2 files changed, 4 insertions(+) diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs index 82fd4da042..ddab161886 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.Win32; using starsky.foundation.native.OpenApplicationNative; @@ -81,6 +82,7 @@ public void Service_OpenDefault_HappyFlow__WindowsOnly() public void OpenApplicationAtUrl_ZeroItems_SoFalse() { var result = new OpenApplicationNativeService().OpenApplicationAtUrl([], "app"); + Console.WriteLine($"result: {result}"); Assert.IsFalse(result); } @@ -88,6 +90,7 @@ public void OpenApplicationAtUrl_ZeroItems_SoFalse() public void OpenDefault_ZeroItemsSo_False() { var result = new OpenApplicationNativeService().OpenDefault([]); + Console.WriteLine($"result: {result}"); Assert.IsFalse(result); } } diff --git a/starsky/starskytest/starsky.foundation.native/Trash/Helpers/WindowsShellTrashBindingHelperTest.cs b/starsky/starskytest/starsky.foundation.native/Trash/Helpers/WindowsShellTrashBindingHelperTest.cs index 67ef6d98a7..ad57a26f11 100644 --- a/starsky/starskytest/starsky.foundation.native/Trash/Helpers/WindowsShellTrashBindingHelperTest.cs +++ b/starsky/starskytest/starsky.foundation.native/Trash/Helpers/WindowsShellTrashBindingHelperTest.cs @@ -239,6 +239,7 @@ public void DriveHasRecycleBin_C_Drive() Assert.AreEqual(0, items); Assert.AreEqual(false, driveHasBin); Assert.IsTrue(info.Contains("Unable to load shared library")); + Assert.Inconclusive("Shell32.dll is not available on Linux or Mac OS"); return; } From c494074a96979d1f1c98bfc57296d47d05587caf Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 14 Feb 2024 22:18:35 +0100 Subject: [PATCH 024/125] try this --- .../Helpers/WindowsOpenDesktopAppTests.cs | 20 +++++++++++++------ .../OpenApplicationNativeServiceTest.cs | 20 +++++++++++++++---- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs index 79376456f0..2fcc38d2cd 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.Win32; using starsky.foundation.native.OpenApplicationNative.Helpers; @@ -80,12 +81,19 @@ public async Task W_OpenDefault_HappyFlow__WindowsOnly() return; } - var mock = new CreateFakeStarskyWindowsExe(); - - await Task.Delay(50); - + var mock = SetupEnsureAssociationsSet(); var result = WindowsOpenDesktopApp.OpenDefault([mock.StarskyDotStarskyPath], OSPlatform.Windows); + + // retry due due multi threading + if ( result != true ) + { + Console.WriteLine("retry due due multi threading"); + await Task.Delay(100); + SetupEnsureAssociationsSet(); + result = WindowsOpenDesktopApp.OpenDefault([mock.StarskyDotStarskyPath]); + } + Assert.IsTrue(result); } @@ -130,10 +138,10 @@ public async Task W_OpenApplicationAtUrl_ReturnsTrue_WhenApplicationOpens__UnixO // Arrange var mock = new CreateFakeStarskyUnixBash(); var fileUrls = new List { mock.StarskyDotStarskyPath, }; - + await Command.Run("chmod", "+x", mock.FullFilePath).Task; - + // Act var result = WindowsOpenDesktopApp.OpenApplicationAtUrl(fileUrls, mock.FullFilePath); diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs index ddab161886..2365a1928e 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.Win32; using starsky.foundation.native.OpenApplicationNative; @@ -12,7 +13,7 @@ namespace starskytest.starsky.foundation.native.OpenApplicationNative public class OpenApplicationNativeServiceTest { private const string Extension = ".starsky"; - private const string ProgId = "starskytest"; + private const string ProgramId = "starskytest"; private const string FileTypeDescription = "Starsky Test File"; [TestInitialize] @@ -34,7 +35,7 @@ private static CreateFakeStarskyWindowsExe SetupEnsureAssociationsSet() new FileAssociation { Extension = Extension, - ProgId = ProgId, + ProgId = ProgramId, FileTypeDescription = FileTypeDescription, ExecutableFilePath = filePath }); @@ -58,11 +59,11 @@ private static void CleanSetup() // Ensure no keys exist before the test starts Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{Extension}", false); - Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{ProgId}", false); + Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{ProgramId}", false); } [TestMethod] - public void Service_OpenDefault_HappyFlow__WindowsOnly() + public async Task Service_OpenDefault_HappyFlow__WindowsOnly() { if ( !new AppSettings().IsWindows ) { @@ -74,6 +75,17 @@ public void Service_OpenDefault_HappyFlow__WindowsOnly() var result = new OpenApplicationNativeService().OpenDefault([mock.StarskyDotStarskyPath]); + + // retry due due multi threading + if ( result != true ) + { + Console.WriteLine("retry due due multi threading"); + await Task.Delay(100); + SetupEnsureAssociationsSet(); + var service = new OpenApplicationNativeService(); + result = service.OpenDefault([mock.StarskyDotStarskyPath]); + } + Assert.IsTrue(result); } From 22f63fda021caa09116394381bf3fc6ddf091507 Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 14 Feb 2024 22:29:54 +0100 Subject: [PATCH 025/125] add fix linux/freebsd --- .../OpenApplicationNativeServiceTest.cs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs index 2365a1928e..ee8059497f 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs @@ -1,7 +1,9 @@ using System; +using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.Win32; +using starsky.foundation.native.Helpers; using starsky.foundation.native.OpenApplicationNative; using starsky.foundation.native.OpenApplicationNative.Helpers; using starsky.foundation.platform.Models; @@ -94,7 +96,15 @@ public async Task Service_OpenDefault_HappyFlow__WindowsOnly() public void OpenApplicationAtUrl_ZeroItems_SoFalse() { var result = new OpenApplicationNativeService().OpenApplicationAtUrl([], "app"); - Console.WriteLine($"result: {result}"); + + // Linux and FreeBSD are not supported + if ( OperatingSystemHelper.GetPlatform() == OSPlatform.Linux || + OperatingSystemHelper.GetPlatform() == OSPlatform.FreeBSD ) + { + Assert.IsNull(result); + return; + } + Assert.IsFalse(result); } @@ -102,7 +112,15 @@ public void OpenApplicationAtUrl_ZeroItems_SoFalse() public void OpenDefault_ZeroItemsSo_False() { var result = new OpenApplicationNativeService().OpenDefault([]); - Console.WriteLine($"result: {result}"); + + // Linux and FreeBSD are not supported + if ( OperatingSystemHelper.GetPlatform() == OSPlatform.Linux || + OperatingSystemHelper.GetPlatform() == OSPlatform.FreeBSD ) + { + Assert.IsNull(result); + return; + } + Assert.IsFalse(result); } } From 7215360c2b459980be116099b5754edecdefaa52 Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 14 Feb 2024 22:31:52 +0100 Subject: [PATCH 026/125] add comments --- .../OpenApplicationNative/OpenApplicationNativeService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs b/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs index bef8259271..856ab90798 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs @@ -9,11 +9,11 @@ namespace starsky.foundation.native.OpenApplicationNative; public class OpenApplicationNativeService : IOpenApplicationNativeService { /// - /// + /// Open file with specified application /// /// full path style /// applicationUrl - /// operation succeed (NOT if file is gone) + /// true is operation succeed, false failed | null is platform unsupported public bool? OpenApplicationAtUrl(List fullPaths, string applicationUrl) { var currentPlatform = OperatingSystemHelper.GetPlatform(); @@ -27,10 +27,10 @@ public class OpenApplicationNativeService : IOpenApplicationNativeService } /// - /// + /// Open file with default application /// /// full path style - /// operation succeed (NOT if file is gone) + /// true is operation succeed, false failed | null is platform unsupported public bool? OpenDefault(List fullPaths) { var currentPlatform = OperatingSystemHelper.GetPlatform(); From 0b15c6696e3f57b5d4180967a1906acc13703382 Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 14 Feb 2024 22:38:41 +0100 Subject: [PATCH 027/125] rename reference --- .../{MacOsStub.cpp => MacOsStub.cpp-reference} | 0 .../{MacOsStub.h => MacOsStub.h-reference} | 0 .../CreateOpenApplicationNative.reference/readme.md | 8 ++++++-- 3 files changed, 6 insertions(+), 2 deletions(-) rename starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/{MacOsStub.cpp => MacOsStub.cpp-reference} (100%) rename starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/{MacOsStub.h => MacOsStub.h-reference} (100%) diff --git a/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/MacOsStub.cpp b/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/MacOsStub.cpp-reference similarity index 100% rename from starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/MacOsStub.cpp rename to starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/MacOsStub.cpp-reference diff --git a/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/MacOsStub.h b/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/MacOsStub.h-reference similarity index 100% rename from starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/MacOsStub.h rename to starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/MacOsStub.h-reference diff --git a/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/readme.md b/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/readme.md index 3d9c4a7499..16cf7a5c96 100644 --- a/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/readme.md +++ b/starsky/starskytest/FakeCreateAn/CreateOpenApplicationNative.reference/readme.md @@ -1,8 +1,12 @@ +For reference only +**Not working correctly yet** + Visual studio installer -> Modify -> Desktop Enviorment with C++ - -To compile the C++ code using the command line on Windows, you can use the Visual Studio Command Prompt, which sets up the necessary environment variables and paths for using the Visual C++ compiler (cl.exe) and other tools. Here's how you can compile the code: +To compile the C++ code using the command line on Windows, you can use the Visual Studio Command +Prompt, which sets up the necessary environment variables and paths for using the Visual C++ +compiler (cl.exe) and other tools. Here's how you can compile the code: Open the Visual Studio Command Prompt: Press the Windows key and type "Visual Studio Command Prompt". From 93bd439b84d9d593ef1ade84c45afd1979344d33 Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 14 Feb 2024 22:39:53 +0100 Subject: [PATCH 028/125] just nuget --- starsky/nuget.config | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/starsky/nuget.config b/starsky/nuget.config index 7db60a6aae..186a8a119e 100644 --- a/starsky/nuget.config +++ b/starsky/nuget.config @@ -1,11 +1,9 @@ - - - - - - - - + + + + + + From 228033475113f6f1c27a6d6e94ab211ea02bde80 Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 14 Feb 2024 22:55:48 +0100 Subject: [PATCH 029/125] format && give more time --- .../Services/StructureService.cs | 59 ++++++++++++------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/starsky/starsky.foundation.storage/Services/StructureService.cs b/starsky/starsky.foundation.storage/Services/StructureService.cs index 60d71aa6cc..ee0bd5d658 100644 --- a/starsky/starsky.foundation.storage/Services/StructureService.cs +++ b/starsky/starsky.foundation.storage/Services/StructureService.cs @@ -36,8 +36,10 @@ public string ParseFileName(DateTime dateTime, CheckStructureFormat(); var fileName = FilenamesHelper.GetFileName(_structure); var fileNameStructure = PathHelper.PrefixDbSlash(fileName); - var parsedStructuredList = ParseStructure(fileNameStructure, dateTime, fileNameBase, extensionWithoutDot); - return PathHelper.RemovePrefixDbSlash(ApplyStructureRangeToStorage(parsedStructuredList)); + var parsedStructuredList = ParseStructure(fileNameStructure, dateTime, fileNameBase, + extensionWithoutDot); + return PathHelper.RemovePrefixDbSlash( + ApplyStructureRangeToStorage(parsedStructuredList)); } /// @@ -66,7 +68,8 @@ public string ParseSubfolders(DateTime dateTime, string fileNameBase = "", string extensionWithoutDot = "") { CheckStructureFormat(); - var parsedStructuredList = ParseStructure(_structure, dateTime, fileNameBase, extensionWithoutDot); + var parsedStructuredList = + ParseStructure(_structure, dateTime, fileNameBase, extensionWithoutDot); return ApplyStructureRangeToStorage( parsedStructuredList.GetRange(0, parsedStructuredList.Count - 1)); @@ -83,7 +86,6 @@ private string ApplyStructureRangeToStorage(List> parsedStr var parentFolderBuilder = new StringBuilder(); foreach ( var subStructureItem in parsedStructuredList ) { - var currentChildFolderBuilder = new StringBuilder(); currentChildFolderBuilder.Append('/'); @@ -92,21 +94,23 @@ private string ApplyStructureRangeToStorage(List> parsedStr currentChildFolderBuilder.Append(structureItem.Output); } - var parentFolderSubPath = FilenamesHelper.GetParentPath(parentFolderBuilder.ToString()); + var parentFolderSubPath = + FilenamesHelper.GetParentPath(parentFolderBuilder.ToString()); var existParentFolder = _storage.ExistFolder(parentFolderSubPath); // default situation without asterisk or child directory is NOT found if ( !currentChildFolderBuilder.ToString().Contains('*') || !existParentFolder ) { - var currentChildFolderRemovedAsterisk = RemoveAsteriskFromString(currentChildFolderBuilder); + var currentChildFolderRemovedAsterisk = + RemoveAsteriskFromString(currentChildFolderBuilder); parentFolderBuilder.Append(currentChildFolderRemovedAsterisk); continue; } parentFolderBuilder = MatchChildDirectories(parentFolderBuilder, currentChildFolderBuilder); - } + return parentFolderBuilder.ToString(); } @@ -118,10 +122,12 @@ private string ApplyStructureRangeToStorage(List> parsedStr /// the current folder name with asterisk /// other child folder items (item in loop of childDirectories) /// is match - private static bool MatchChildFolderSearch(StringBuilder parentFolderBuilder, StringBuilder currentChildFolderBuilder, string p) + private static bool MatchChildFolderSearch(StringBuilder parentFolderBuilder, + StringBuilder currentChildFolderBuilder, string p) { var matchDirectFolderName = RemoveAsteriskFromString(currentChildFolderBuilder); - if ( matchDirectFolderName != "/" && p == parentFolderBuilder + matchDirectFolderName ) return true; + if ( matchDirectFolderName != "/" && p == parentFolderBuilder + matchDirectFolderName ) + return true; var matchRegex = new Regex( parentFolderBuilder + currentChildFolderBuilder.ToString().Replace("*", ".+"), @@ -136,14 +142,15 @@ private static bool MatchChildFolderSearch(StringBuilder parentFolderBuilder, St /// parent folder (subPath style) /// child folder with asterisk /// SubPath without asterisk - private StringBuilder MatchChildDirectories(StringBuilder parentFolderBuilder, StringBuilder currentChildFolderBuilder) + private StringBuilder MatchChildDirectories(StringBuilder parentFolderBuilder, + StringBuilder currentChildFolderBuilder) { // should return a list of: var childDirectories = _storage.GetDirectories(parentFolderBuilder.ToString()).ToList(); var matchingFoldersPath = childDirectories.Find(p => MatchChildFolderSearch(parentFolderBuilder, currentChildFolderBuilder, p) - ); + ); // When a new folder with asterisk is created if ( matchingFoldersPath == null ) @@ -154,6 +161,7 @@ private StringBuilder MatchChildDirectories(StringBuilder parentFolderBuilder, S { defaultValue = "/default"; } + parentFolderBuilder.Append(defaultValue); return parentFolderBuilder; } @@ -183,13 +191,14 @@ private static string RemoveAsteriskFromString(StringBuilder input) private void CheckStructureFormat() { if ( !string.IsNullOrEmpty(_structure) && - _structure.StartsWith('/') && _structure.EndsWith(".ext") && - _structure != "/.ext" ) + _structure.StartsWith('/') && _structure.EndsWith(".ext") && + _structure != "/.ext" ) { return; } - throw new FieldAccessException("Structure is not right formatted, please read the documentation"); + throw new FieldAccessException( + "Structure is not right formatted, please read the documentation"); } /// @@ -197,7 +206,8 @@ private void CheckStructureFormat() /// @see: https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings /// Not escaped regex: \\?(d{1,4}|f{1,6}|F{1,6}|g{1,2}|h{1,2}|H{1,2}|K|m{1,2}|M{1,4}|s{1,2}|t{1,2}|y{1,5}|z{1,3}) /// - const string DateRegexPattern = "\\\\?(d{1,4}|f{1,6}|F{1,6}|g{1,2}|h{1,2}|H{1,2}|K|m{1,2}|M{1,4}|s{1,2}|t{1,2}|y{1,5}|z{1,3})"; + const string DateRegexPattern = + "\\\\?(d{1,4}|f{1,6}|F{1,6}|g{1,2}|h{1,2}|H{1,2}|K|m{1,2}|M{1,4}|s{1,2}|t{1,2}|y{1,5}|z{1,3})"; /// /// Parse the dateTime structure input to the dateTime provided @@ -207,7 +217,8 @@ private void CheckStructureFormat() /// source name, can be used in the options /// fileExtension without dot /// Object with Structure Range output - private static List> ParseStructure(string structure, DateTime dateTime, + private static List> ParseStructure(string structure, + DateTime dateTime, string fileNameBase = "", string extensionWithoutDot = "") { var structureList = structure.Split('/'); @@ -219,8 +230,8 @@ private static List> ParseStructure(string structure, DateT var matchCollection = new Regex(DateRegexPattern + "|{filenamebase}|\\*|.ext|.", - RegexOptions.None, TimeSpan.FromMilliseconds(100)) - .Matches(structureItem); + RegexOptions.None, TimeSpan.FromMilliseconds(200)) + .Matches(structureItem); var matchList = new List(); foreach ( Match match in matchCollection ) @@ -230,7 +241,8 @@ private static List> ParseStructure(string structure, DateT Pattern = match.Value, Start = match.Index, End = match.Index + match.Length, - Output = OutputStructureRangeItemParser(match.Value, dateTime, fileNameBase, extensionWithoutDot) + Output = OutputStructureRangeItemParser(match.Value, dateTime, + fileNameBase, extensionWithoutDot) }); } @@ -252,13 +264,14 @@ private static string OutputStructureRangeItemParser(string pattern, DateTime da string fileNameBase, string extensionWithoutDot = "") { // allow only full word matches (so .ext is no match) - MatchCollection matchCollection = new Regex(DateRegexPattern, + var matchCollection = new Regex(DateRegexPattern, RegexOptions.None, TimeSpan.FromMilliseconds(100)).Matches(pattern); foreach ( Match match in matchCollection ) { // Ignore escaped items - if ( !match.Value.StartsWith('\\') && match.Index == 0 && match.Length == pattern.Length ) + if ( !match.Value.StartsWith('\\') && match.Index == 0 && + match.Length == pattern.Length ) { return dateTime.ToString(pattern, CultureInfo.InvariantCulture); } @@ -270,7 +283,9 @@ private static string OutputStructureRangeItemParser(string pattern, DateTime da case "{filenamebase}": return fileNameBase; case ".ext": - return string.IsNullOrEmpty(extensionWithoutDot) ? ".unknown" : $".{extensionWithoutDot}"; + return string.IsNullOrEmpty(extensionWithoutDot) + ? ".unknown" + : $".{extensionWithoutDot}"; default: return pattern.Replace("\\", string.Empty); } From 219355b9f43fa340979b562e6b4f8df3e1bb40f5 Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 15 Feb 2024 16:19:28 +0100 Subject: [PATCH 030/125] add project && code formatting --- .../Interfaces/IOpenEditorDesktopService.cs | 5 + .../Service/OpenEditorDesktopService.cs | 25 ++ .../starsky.feature.desktop.csproj | 19 + .../Helpers/AppSettingsCompareHelper.cs | 167 ++++--- .../Helpers/SetupAppSettings.cs | 8 +- .../Models/AppSettings.cs | 33 +- .../AppSettingsDefaultEditorApplication.cs | 19 + starsky/starsky.sln | 7 + .../AppSettingsFeaturesController.cs | 5 +- .../Controllers/AppSettingsControllerTest.cs | 115 ++--- .../AppSettingsFeaturesControllerTest.cs | 22 +- .../Helpers/AppSettingsCompareHelperTest.cs | 407 ++++++++++-------- 12 files changed, 496 insertions(+), 336 deletions(-) create mode 100644 starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs create mode 100644 starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs create mode 100644 starsky/starsky.feature.desktop/starsky.feature.desktop.csproj create mode 100644 starsky/starsky.foundation.platform/Models/AppSettingsDefaultEditorApplication.cs diff --git a/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs b/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs new file mode 100644 index 0000000000..eef6f36ad2 --- /dev/null +++ b/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs @@ -0,0 +1,5 @@ +namespace starsky.feature.desktop.Interfaces; + +public interface IOpenEditorDesktopService +{ +} diff --git a/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs b/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs new file mode 100644 index 0000000000..b18c6983dc --- /dev/null +++ b/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs @@ -0,0 +1,25 @@ +using starsky.feature.desktop.Interfaces; +using starsky.foundation.injection; +using starsky.foundation.native.OpenApplicationNative.Interfaces; +using starsky.foundation.platform.Models; + +namespace starsky.feature.desktop.Service; + +[Service(typeof(IOpenEditorDesktopService), InjectionLifetime = InjectionLifetime.Scoped)] +public class OpenEditorDesktopService : IOpenEditorDesktopService +{ + private readonly AppSettings _appSettings; + private readonly IOpenApplicationNativeService _openApplicationNativeService; + + public OpenEditorDesktopService(AppSettings appSettings, + IOpenApplicationNativeService openApplicationNativeService) + { + _appSettings = appSettings; + _openApplicationNativeService = openApplicationNativeService; + } + + public void Test() + { + _appSettings.UseLocalDesktop + } +} diff --git a/starsky/starsky.feature.desktop/starsky.feature.desktop.csproj b/starsky/starsky.feature.desktop/starsky.feature.desktop.csproj new file mode 100644 index 0000000000..900d7dc1d3 --- /dev/null +++ b/starsky/starsky.feature.desktop/starsky.feature.desktop.csproj @@ -0,0 +1,19 @@ +ο»Ώ + + + net8.0 + + {ca693c59-e50a-4dc6-a2ba-fe7e0caf417a} + 0.6.0-beta.0 + enable + enable + starsky.feature.desktop + + + + + + + + + diff --git a/starsky/starsky.foundation.platform/Helpers/AppSettingsCompareHelper.cs b/starsky/starsky.foundation.platform/Helpers/AppSettingsCompareHelper.cs index dcf80bef8c..936ff1517c 100644 --- a/starsky/starsky.foundation.platform/Helpers/AppSettingsCompareHelper.cs +++ b/starsky/starsky.foundation.platform/Helpers/AppSettingsCompareHelper.cs @@ -10,7 +10,6 @@ namespace starsky.foundation.platform.Helpers { public static class AppSettingsCompareHelper { - /// /// Compare a fileIndex item and update items if there are changed in the updateObject /// append => (propertyName == "Tags" add it with comma space or with single space) @@ -21,8 +20,10 @@ public static class AppSettingsCompareHelper public static List Compare(AppSettings sourceIndexItem, object? updateObject = null) { updateObject ??= new AppSettings(); - var propertiesA = sourceIndexItem.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); - var propertiesB = updateObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); + var propertiesA = sourceIndexItem.GetType() + .GetProperties(BindingFlags.Public | BindingFlags.Instance); + var propertiesB = updateObject.GetType() + .GetProperties(BindingFlags.Public | BindingFlags.Instance); var differenceList = new List(); foreach ( var propertyB in propertiesB ) @@ -31,42 +32,55 @@ public static List Compare(AppSettings sourceIndexItem, object? updateOb var propertyInfoFromA = Array.Find(propertiesA, p => p.Name == propertyB.Name); if ( propertyInfoFromA == null ) continue; - CompareMultipleSingleItems(propertyB, propertyInfoFromA, sourceIndexItem, updateObject, differenceList); - CompareMultipleListDictionary(propertyB, propertyInfoFromA, sourceIndexItem, updateObject, differenceList); - CompareMultipleObjects(propertyB, propertyInfoFromA, sourceIndexItem, updateObject, differenceList); - + CompareMultipleSingleItems(propertyB, propertyInfoFromA, sourceIndexItem, + updateObject, differenceList); + CompareMultipleListDictionary(propertyB, propertyInfoFromA, sourceIndexItem, + updateObject, differenceList); + CompareMultipleObjects(propertyB, propertyInfoFromA, sourceIndexItem, updateObject, + differenceList); } + return differenceList; } - private static void CompareMultipleObjects(PropertyInfo propertyB, PropertyInfo propertyInfoFromA, AppSettings sourceIndexItem, object updateObject, List differenceList) + private static void CompareMultipleObjects(PropertyInfo propertyB, + PropertyInfo propertyInfoFromA, AppSettings sourceIndexItem, object updateObject, + List differenceList) { - if ( propertyInfoFromA.PropertyType == typeof(OpenTelemetrySettings) && propertyB.PropertyType == typeof(OpenTelemetrySettings) ) + if ( propertyInfoFromA.PropertyType == typeof(OpenTelemetrySettings) && + propertyB.PropertyType == typeof(OpenTelemetrySettings) ) { - var oldObjectValue = ( OpenTelemetrySettings? )propertyInfoFromA.GetValue(sourceIndexItem, null); - var newObjectValue = ( OpenTelemetrySettings? )propertyB.GetValue(updateObject, null); - CompareOpenTelemetrySettingsObject(propertyB.Name, sourceIndexItem, oldObjectValue, newObjectValue, differenceList); + var oldObjectValue = + ( OpenTelemetrySettings? )propertyInfoFromA.GetValue(sourceIndexItem, null); + var newObjectValue = + ( OpenTelemetrySettings? )propertyB.GetValue(updateObject, null); + CompareOpenTelemetrySettingsObject(propertyB.Name, sourceIndexItem, oldObjectValue, + newObjectValue, differenceList); } } - [SuppressMessage("Performance", "CA1859:Use concrete types when possible for improved performance")] - private static void CompareOpenTelemetrySettingsObject(string propertyName, AppSettings? sourceIndexItem, + [SuppressMessage("Performance", + "CA1859:Use concrete types when possible for improved performance")] + private static void CompareOpenTelemetrySettingsObject(string propertyName, + AppSettings? sourceIndexItem, OpenTelemetrySettings? oldKeyValuePairStringStringValue, - OpenTelemetrySettings? newKeyValuePairStringStringValue, ICollection differenceList) + OpenTelemetrySettings? newKeyValuePairStringStringValue, + ICollection differenceList) { if ( oldKeyValuePairStringStringValue == null || - newKeyValuePairStringStringValue == null || - // compare lists - JsonSerializer.Serialize(oldKeyValuePairStringStringValue) == - JsonSerializer.Serialize(newKeyValuePairStringStringValue) || - // default options - JsonSerializer.Serialize(newKeyValuePairStringStringValue) == - JsonSerializer.Serialize(new OpenTelemetrySettings()) ) + newKeyValuePairStringStringValue == null || + // compare lists + JsonSerializer.Serialize(oldKeyValuePairStringStringValue) == + JsonSerializer.Serialize(newKeyValuePairStringStringValue) || + // default options + JsonSerializer.Serialize(newKeyValuePairStringStringValue) == + JsonSerializer.Serialize(new OpenTelemetrySettings()) ) { return; } - sourceIndexItem?.GetType().GetProperty(propertyName)?.SetValue(sourceIndexItem, newKeyValuePairStringStringValue, null); + sourceIndexItem?.GetType().GetProperty(propertyName)?.SetValue(sourceIndexItem, + newKeyValuePairStringStringValue, null); differenceList.Add(propertyName.ToLowerInvariant()); } @@ -75,31 +89,38 @@ private static void CompareMultipleSingleItems(PropertyInfo propertyB, AppSettings sourceIndexItem, object updateObject, List differenceList) { - if ( propertyInfoFromA.PropertyType == typeof(bool?) && propertyB.PropertyType == typeof(bool?) ) + if ( propertyInfoFromA.PropertyType == typeof(bool?) && + propertyB.PropertyType == typeof(bool?) ) { var oldBoolValue = ( bool? )propertyInfoFromA.GetValue(sourceIndexItem, null); var newBoolValue = ( bool? )propertyB.GetValue(updateObject, null); - CompareBool(propertyB.Name, sourceIndexItem, oldBoolValue, newBoolValue, differenceList); + CompareBool(propertyB.Name, sourceIndexItem, oldBoolValue, newBoolValue, + differenceList); } if ( propertyB.PropertyType == typeof(string) ) { var oldStringValue = ( string? )propertyInfoFromA.GetValue(sourceIndexItem, null); var newStringValue = ( string? )propertyB.GetValue(updateObject, null); - CompareString(propertyB.Name, sourceIndexItem, oldStringValue!, newStringValue!, differenceList); + CompareString(propertyB.Name, sourceIndexItem, oldStringValue!, newStringValue!, + differenceList); } if ( propertyB.PropertyType == typeof(int) ) { var oldIntValue = ( int )propertyInfoFromA.GetValue(sourceIndexItem, null)!; var newIntValue = ( int )propertyB.GetValue(updateObject, null)!; - CompareInt(propertyB.Name, sourceIndexItem, oldIntValue, newIntValue, differenceList); + CompareInt(propertyB.Name, sourceIndexItem, oldIntValue, newIntValue, + differenceList); } if ( propertyB.PropertyType == typeof(AppSettings.DatabaseTypeList) ) { - var oldListStringValue = ( AppSettings.DatabaseTypeList? )propertyInfoFromA.GetValue(sourceIndexItem, null); - var newListStringValue = ( AppSettings.DatabaseTypeList? )propertyB.GetValue(updateObject, null); + var oldListStringValue = + ( AppSettings.DatabaseTypeList? )propertyInfoFromA.GetValue(sourceIndexItem, + null); + var newListStringValue = + ( AppSettings.DatabaseTypeList? )propertyB.GetValue(updateObject, null); CompareDatabaseTypeList(propertyB.Name, sourceIndexItem, oldListStringValue, newListStringValue, differenceList); } @@ -111,7 +132,8 @@ private static void CompareMultipleListDictionary(PropertyInfo propertyB, { if ( propertyB.PropertyType == typeof(List) ) { - var oldListStringValue = ( List? )propertyInfoFromA.GetValue(sourceIndexItem, null); + var oldListStringValue = + ( List? )propertyInfoFromA.GetValue(sourceIndexItem, null); var newListStringValue = ( List? )propertyB.GetValue(updateObject, null); CompareListString(propertyB.Name, sourceIndexItem, oldListStringValue, newListStringValue, differenceList); @@ -119,19 +141,26 @@ private static void CompareMultipleListDictionary(PropertyInfo propertyB, if ( propertyB.PropertyType == typeof(List) ) { - var oldKeyValuePairStringStringValue = ( List? )propertyInfoFromA.GetValue(sourceIndexItem, null); - var newKeyValuePairStringStringValue = ( List? )propertyB.GetValue(updateObject, null); - CompareKeyValuePairStringString(propertyB.Name, sourceIndexItem, oldKeyValuePairStringStringValue!, + var oldKeyValuePairStringStringValue = + ( List? )propertyInfoFromA.GetValue(sourceIndexItem, null); + var newKeyValuePairStringStringValue = + ( List? )propertyB.GetValue(updateObject, null); + CompareKeyValuePairStringString(propertyB.Name, sourceIndexItem, + oldKeyValuePairStringStringValue!, newKeyValuePairStringStringValue!, differenceList); } - if ( propertyB.PropertyType == typeof(Dictionary>) ) + if ( propertyB.PropertyType == + typeof(Dictionary>) ) { - var oldListPublishProfilesValue = ( Dictionary>? ) + var oldListPublishProfilesValue = + ( Dictionary>? ) propertyInfoFromA.GetValue(sourceIndexItem, null); - var newListPublishProfilesValue = ( Dictionary>? ) + var newListPublishProfilesValue = + ( Dictionary>? ) propertyB.GetValue(updateObject, null); - CompareListPublishProfiles(propertyB.Name, sourceIndexItem, oldListPublishProfilesValue, + CompareListPublishProfiles(propertyB.Name, sourceIndexItem, + oldListPublishProfilesValue, newListPublishProfilesValue, differenceList); } @@ -146,40 +175,45 @@ private static void CompareMultipleListDictionary(PropertyInfo propertyB, } } - private static void CompareStringDictionary(string propertyName, AppSettings sourceIndexItem, + private static void CompareStringDictionary(string propertyName, + AppSettings sourceIndexItem, Dictionary? oldDictionaryValue, Dictionary? newDictionaryValue, List differenceList) { if ( oldDictionaryValue == null || newDictionaryValue?.Count == 0 ) return; if ( JsonSerializer.Serialize(oldDictionaryValue, - DefaultJsonSerializer.CamelCase) == JsonSerializer.Serialize(newDictionaryValue, - DefaultJsonSerializer.CamelCase) ) + DefaultJsonSerializer.CamelCase) == JsonSerializer.Serialize(newDictionaryValue, + DefaultJsonSerializer.CamelCase) ) { return; } - sourceIndexItem.GetType().GetProperty(propertyName)?.SetValue(sourceIndexItem, newDictionaryValue, null); + sourceIndexItem.GetType().GetProperty(propertyName) + ?.SetValue(sourceIndexItem, newDictionaryValue, null); differenceList.Add(propertyName.ToLowerInvariant()); } - private static void CompareKeyValuePairStringString(string propertyName, AppSettings sourceIndexItem, + private static void CompareKeyValuePairStringString(string propertyName, + AppSettings sourceIndexItem, List? oldKeyValuePairStringStringValue, - List? newKeyValuePairStringStringValue, List differenceList) + List? newKeyValuePairStringStringValue, + List differenceList) { if ( oldKeyValuePairStringStringValue == null || - newKeyValuePairStringStringValue == null || - newKeyValuePairStringStringValue.Count == 0 ) + newKeyValuePairStringStringValue == null || + newKeyValuePairStringStringValue.Count == 0 ) { return; } if ( oldKeyValuePairStringStringValue.Equals( - newKeyValuePairStringStringValue) ) + newKeyValuePairStringStringValue) ) { return; } - sourceIndexItem.GetType().GetProperty(propertyName)?.SetValue(sourceIndexItem, newKeyValuePairStringStringValue, null); + sourceIndexItem.GetType().GetProperty(propertyName)?.SetValue(sourceIndexItem, + newKeyValuePairStringStringValue, null); differenceList.Add(propertyName.ToLowerInvariant()); } @@ -191,12 +225,15 @@ private static void CompareKeyValuePairStringString(string propertyName, AppSett /// oldDatabaseTypeList to compare with newDatabaseTypeList /// newDatabaseTypeList to compare with oldDatabaseTypeList /// list of different values - internal static void CompareDatabaseTypeList(string propertyName, AppSettings sourceIndexItem, + internal static void CompareDatabaseTypeList(string propertyName, + AppSettings sourceIndexItem, AppSettings.DatabaseTypeList? oldDatabaseTypeList, AppSettings.DatabaseTypeList? newDatabaseTypeList, List differenceList) { - if ( oldDatabaseTypeList == newDatabaseTypeList || newDatabaseTypeList == new AppSettings().DatabaseType ) return; - sourceIndexItem.GetType().GetProperty(propertyName)?.SetValue(sourceIndexItem, newDatabaseTypeList, null); + if ( oldDatabaseTypeList == newDatabaseTypeList || + newDatabaseTypeList == new AppSettings().DatabaseType ) return; + sourceIndexItem.GetType().GetProperty(propertyName) + ?.SetValue(sourceIndexItem, newDatabaseTypeList, null); differenceList.Add(propertyName.ToLowerInvariant()); } @@ -210,17 +247,19 @@ internal static void CompareDatabaseTypeList(string propertyName, AppSettings so /// newListStringValue to compare with oldListStringValue /// list of different values internal static void CompareListString(string propertyName, AppSettings sourceIndexItem, - List? oldListStringValue, List? newListStringValue, List differenceList) + List? oldListStringValue, List? newListStringValue, + List differenceList) { if ( oldListStringValue == null || newListStringValue?.Count == 0 ) return; if ( JsonSerializer.Serialize(oldListStringValue, - DefaultJsonSerializer.CamelCase) == JsonSerializer.Serialize(newListStringValue, - DefaultJsonSerializer.CamelCase) ) + DefaultJsonSerializer.CamelCase) == JsonSerializer.Serialize(newListStringValue, + DefaultJsonSerializer.CamelCase) ) { return; } - sourceIndexItem.GetType().GetProperty(propertyName)?.SetValue(sourceIndexItem, newListStringValue, null); + sourceIndexItem.GetType().GetProperty(propertyName) + ?.SetValue(sourceIndexItem, newListStringValue, null); differenceList.Add(propertyName.ToLowerInvariant()); } @@ -232,14 +271,17 @@ internal static void CompareListString(string propertyName, AppSettings sourceIn /// oldListPublishValue to compare with newListPublishValue /// newListPublishValue to compare with oldListPublishValue /// list of different values - internal static void CompareListPublishProfiles(string propertyName, AppSettings sourceIndexItem, + internal static void CompareListPublishProfiles(string propertyName, + AppSettings sourceIndexItem, Dictionary>? oldListPublishValue, - Dictionary>? newListPublishValue, List differenceList) + Dictionary>? newListPublishValue, + List differenceList) { if ( oldListPublishValue == null || newListPublishValue?.Count == 0 ) return; if ( oldListPublishValue.Equals(newListPublishValue) ) return; - sourceIndexItem.GetType().GetProperty(propertyName)?.SetValue(sourceIndexItem, newListPublishValue, null); + sourceIndexItem.GetType().GetProperty(propertyName) + ?.SetValue(sourceIndexItem, newListPublishValue, null); differenceList.Add(propertyName.ToLowerInvariant()); } @@ -252,18 +294,22 @@ internal static void CompareListPublishProfiles(string propertyName, AppSettings /// oldBoolValue to compare with newBoolValue /// oldBoolValue to compare with newBoolValue /// list of different values - internal static void CompareBool(string propertyName, AppSettings sourceIndexItem, bool? oldBoolValue, + internal static void CompareBool(string propertyName, AppSettings sourceIndexItem, + bool? oldBoolValue, bool? newBoolValue, List differenceList) { if ( newBoolValue == null ) { return; } + if ( oldBoolValue == newBoolValue ) { return; } - sourceIndexItem.GetType().GetProperty(propertyName)?.SetValue(sourceIndexItem, newBoolValue, null); + + sourceIndexItem.GetType().GetProperty(propertyName) + ?.SetValue(sourceIndexItem, newBoolValue, null); differenceList.Add(propertyName.ToLowerInvariant()); } @@ -288,7 +334,7 @@ internal static void CompareString(string propertyName, AppSettings sourceIndexI } if ( oldStringValue == newStringValue || - ( string.IsNullOrEmpty(newStringValue) ) ) + ( string.IsNullOrEmpty(newStringValue) ) ) { return; } @@ -338,6 +384,5 @@ internal static void CompareInt(string propertyName, AppSettings sourceIndexItem return Array.Find(car.GetType().GetProperties(), pi => pi.Name == propertyName)? .GetValue(car, null); } - } } diff --git a/starsky/starsky.foundation.platform/Helpers/SetupAppSettings.cs b/starsky/starsky.foundation.platform/Helpers/SetupAppSettings.cs index 26db9cce03..9b709af273 100644 --- a/starsky/starsky.foundation.platform/Helpers/SetupAppSettings.cs +++ b/starsky/starsky.foundation.platform/Helpers/SetupAppSettings.cs @@ -11,11 +11,13 @@ using starsky.foundation.platform.Models; [assembly: InternalsVisibleTo("starskytest")] + namespace starsky.foundation.platform.Helpers { public static class SetupAppSettings { - public static async Task FirstStepToAddSingleton(ServiceCollection services) + public static async Task FirstStepToAddSingleton( + ServiceCollection services) { services.AddSingleton(new ConfigurationBuilder().Build()); var configurationRoot = await AppSettingsToBuilder(); @@ -35,7 +37,8 @@ public static async Task AppSettingsToBuilder(string[]? args var settings = await MergeJsonFiles(appSettings.BaseDirectoryProject); // Make sure is wrapped in a AppContainer app - var utf8Bytes = JsonSerializer.SerializeToUtf8Bytes(new AppContainerAppSettings { App = settings }); + var appContainer = new AppContainerAppSettings { App = settings }; + var utf8Bytes = JsonSerializer.SerializeToUtf8Bytes(appContainer); builder .AddJsonStream(new MemoryStream(utf8Bytes)) @@ -117,6 +120,5 @@ public static AppSettings ConfigurePoCoAppSettings(IServiceCollection services, return serviceProvider.GetRequiredService(); } - } } diff --git a/starsky/starsky.foundation.platform/Models/AppSettings.cs b/starsky/starsky.foundation.platform/Models/AppSettings.cs index 83c1730b31..1df226e878 100644 --- a/starsky/starsky.foundation.platform/Models/AppSettings.cs +++ b/starsky/starsky.foundation.platform/Models/AppSettings.cs @@ -381,7 +381,7 @@ public static void StructureCheck(string? structure) } throw new ArgumentException("(StructureCheck) Structure is not confirm regex - " + - structure); + structure); } /// @@ -587,8 +587,8 @@ public string WebFtp if ( string.IsNullOrEmpty(value) ) return; Uri uriAddress = new Uri(value); if ( uriAddress.UserInfo.Split(":".ToCharArray()).Length == 2 - && uriAddress.Scheme == "ftp" - && uriAddress.LocalPath.Length >= 1 ) + && uriAddress.Scheme == "ftp" + && uriAddress.LocalPath.Length >= 1 ) { _webFtp = value; } @@ -650,8 +650,7 @@ public Dictionary>? PublishProfiles /// Value for AccountRolesDefaultByEmailRegisterOverwrite /// private Dictionary - AccountRolesByEmailRegisterOverwritePrivate - { get; set; } = + AccountRolesByEmailRegisterOverwritePrivate { get; set; } = new Dictionary(); /// @@ -669,7 +668,7 @@ public Dictionary? AccountRolesByEmailRegisterOverwrite { if ( value == null ) return; foreach ( var singleValue in value.Where(singleValue => - AccountRoles.GetAllRoles().Contains(singleValue.Value)) ) + AccountRoles.GetAllRoles().Contains(singleValue.Value)) ) { AccountRolesByEmailRegisterOverwritePrivate.TryAdd( singleValue.Key, singleValue.Value); @@ -761,10 +760,16 @@ public Dictionary? AccountRolesByEmailRegisterOverwrite /// /// Disable logout buttons in UI /// And hides server specific features that are strange on a local desktop + /// Enable Desktop based features /// [PackageTelemetry] - public bool? UseLocalDesktopUi { get; set; } = false; + public bool? UseLocalDesktop { get; set; } = false; + /// + /// Editor by imageFormat + /// + [PackageTelemetry] + public List DefaultDesktopEditor { get; set; } = []; /// /// Helps us improve the software @@ -884,7 +889,7 @@ public AppSettings CloneToDisplay() } if ( appSettings.DatabaseType == DatabaseTypeList.Sqlite && - !string.IsNullOrEmpty(userProfileFolder) ) + !string.IsNullOrEmpty(userProfileFolder) ) { appSettings.DatabaseConnection = appSettings.DatabaseConnection.Replace(userProfileFolder, "~"); @@ -896,7 +901,7 @@ public AppSettings CloneToDisplay() } if ( !string.IsNullOrEmpty(appSettings.AppSettingsPath) && - !string.IsNullOrEmpty(userProfileFolder) ) + !string.IsNullOrEmpty(userProfileFolder) ) { appSettings.AppSettingsPath = appSettings.AppSettingsPath.Replace(userProfileFolder, "~"); @@ -905,7 +910,7 @@ public AppSettings CloneToDisplay() if ( appSettings.PublishProfiles != null ) { foreach ( var value in appSettings.PublishProfiles.SelectMany(profile => - profile.Value) ) + profile.Value) ) { ReplaceAppSettingsPublishProfilesCloneToDisplay(value); } @@ -952,7 +957,7 @@ private static void ReplaceAppSettingsPublishProfilesCloneToDisplay( AppSettingsPublishProfiles value) { if ( !string.IsNullOrEmpty(value.Path) && - value.Path != AppSettingsPublishProfiles.GetDefaultPath() ) + value.Path != AppSettingsPublishProfiles.GetDefaultPath() ) { value.Path = CloneToDisplaySecurityWarning; } @@ -1071,7 +1076,7 @@ internal static string ReplaceEnvironmentVariable(string input) public string SqLiteFullPath(string connectionString, string baseDirectoryProject) { if ( DatabaseType == DatabaseTypeList.Mysql && - string.IsNullOrWhiteSpace(connectionString) ) + string.IsNullOrWhiteSpace(connectionString) ) throw new ArgumentException("The 'DatabaseConnection' field is null or empty"); if ( DatabaseType != DatabaseTypeList.Sqlite ) @@ -1092,7 +1097,7 @@ public string SqLiteFullPath(string connectionString, string baseDirectoryProjec if ( baseDirectoryProject.Contains("entityframeworkcore") ) return connectionString; var dataSource = "Data Source=" + baseDirectoryProject + - Path.DirectorySeparatorChar + databaseFileName; + Path.DirectorySeparatorChar + databaseFileName; return dataSource; } @@ -1121,7 +1126,7 @@ internal static void CopyProperties(object source, object destination) var destinationProperty = destinationType.GetProperty(sourceProperty.Name); if ( destinationProperty == null || - !destinationProperty.CanWrite ) + !destinationProperty.CanWrite ) { continue; } diff --git a/starsky/starsky.foundation.platform/Models/AppSettingsDefaultEditorApplication.cs b/starsky/starsky.foundation.platform/Models/AppSettingsDefaultEditorApplication.cs new file mode 100644 index 0000000000..ca243091d7 --- /dev/null +++ b/starsky/starsky.foundation.platform/Models/AppSettingsDefaultEditorApplication.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using starsky.foundation.platform.Helpers; + +namespace starsky.foundation.platform.Models; + +public class AppSettingsDefaultEditorApplication +{ + /// + /// For what type of files + /// + [JsonConverter(typeof(JsonStringEnumConverter))] + public List ImageFormats { get; set; } = []; + + /// + /// Path to .exe on windows and .app on Mac OS + /// + public string ApplicationPath { get; set; } +} diff --git a/starsky/starsky.sln b/starsky/starsky.sln index 5623f89bd0..0c2506287d 100644 --- a/starsky/starsky.sln +++ b/starsky/starsky.sln @@ -164,6 +164,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "starsky.feature.trash", "st EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "starsky.feature.settings", "starsky.feature.settings\starsky.feature.settings.csproj", "{F2C4C9DE-22A1-4B34-AC1D-0F08353E0742}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "starsky.feature.desktop", "starsky.feature.desktop\starsky.feature.desktop.csproj", "{B88C2815-D154-4C6D-AE37-2E150AEBF73D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -362,6 +364,10 @@ Global {F2C4C9DE-22A1-4B34-AC1D-0F08353E0742}.Debug|Any CPU.Build.0 = Debug|Any CPU {F2C4C9DE-22A1-4B34-AC1D-0F08353E0742}.Release|Any CPU.ActiveCfg = Release|Any CPU {F2C4C9DE-22A1-4B34-AC1D-0F08353E0742}.Release|Any CPU.Build.0 = Release|Any CPU + {B88C2815-D154-4C6D-AE37-2E150AEBF73D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B88C2815-D154-4C6D-AE37-2E150AEBF73D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B88C2815-D154-4C6D-AE37-2E150AEBF73D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B88C2815-D154-4C6D-AE37-2E150AEBF73D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -421,5 +427,6 @@ Global {0072F697-4E18-4B5F-80DF-530361D3E847} = {1C1EB4A5-08D0-4014-AE1F-962642A4E5D3} {A62C129C-5D0C-4A0A-B5AA-261E041FF55D} = {4B9276C3-651E-48D3-B3A7-3F4D74F3D01A} {F2C4C9DE-22A1-4B34-AC1D-0F08353E0742} = {4B9276C3-651E-48D3-B3A7-3F4D74F3D01A} + {B88C2815-D154-4C6D-AE37-2E150AEBF73D} = {4B9276C3-651E-48D3-B3A7-3F4D74F3D01A} EndGlobalSection EndGlobal diff --git a/starsky/starsky/Controllers/AppSettingsFeaturesController.cs b/starsky/starsky/Controllers/AppSettingsFeaturesController.cs index fd7d9dfb06..91462da8fa 100644 --- a/starsky/starsky/Controllers/AppSettingsFeaturesController.cs +++ b/starsky/starsky/Controllers/AppSettingsFeaturesController.cs @@ -11,7 +11,8 @@ public class AppSettingsFeaturesController : Controller private readonly IMoveToTrashService _moveToTrashService; private readonly AppSettings _appSettings; - public AppSettingsFeaturesController(IMoveToTrashService moveToTrashService, AppSettings appSettings) + public AppSettingsFeaturesController(IMoveToTrashService moveToTrashService, + AppSettings appSettings) { _moveToTrashService = moveToTrashService; _appSettings = appSettings; @@ -36,7 +37,7 @@ public IActionResult FeaturesView() var shortAppSettings = new EnvFeaturesViewModel { SystemTrashEnabled = _moveToTrashService.IsEnabled(), - UseLocalDesktopUi = _appSettings.UseLocalDesktopUi == true + UseLocalDesktopUi = _appSettings.UseLocalDesktop == true }; return Json(shortAppSettings); diff --git a/starsky/starskytest/Controllers/AppSettingsControllerTest.cs b/starsky/starskytest/Controllers/AppSettingsControllerTest.cs index 177d824484..6357e57acb 100644 --- a/starsky/starskytest/Controllers/AppSettingsControllerTest.cs +++ b/starsky/starskytest/Controllers/AppSettingsControllerTest.cs @@ -18,16 +18,16 @@ namespace starskytest.Controllers [TestClass] public sealed class AppSettingsControllerTest { - [TestMethod] public void ENV_StarskyTestEnv() { - var controller = new AppSettingsController(new AppSettings(),new FakeIUpdateAppSettingsByPath()); + var controller = + new AppSettingsController(new AppSettings(), new FakeIUpdateAppSettingsByPath()); var actionResult = controller.Env() as JsonResult; var resultAppSettings = actionResult?.Value as AppSettings; Assert.AreEqual("Starsky", resultAppSettings?.Name); } - + [TestMethod] public void ENV_StarskyTestEnv_ForceHtml() { @@ -38,40 +38,45 @@ public void ENV_StarskyTestEnv_ForceHtml() var actionResult = controller.Env() as JsonResult; var resultAppSettings = actionResult?.Value as AppSettings; Assert.AreEqual("Starsky", resultAppSettings?.Name); - Assert.AreEqual("text/html; charset=utf-8", + Assert.AreEqual("text/html; charset=utf-8", controller.ControllerContext.HttpContext.Response.Headers.ContentType.ToString()); } - + [TestMethod] public async Task UpdateAppSettings_Verbose() { var appSettings = new AppSettings(); var storage = new FakeIStorage(new List { "/" }); - var controller = new AppSettingsController(appSettings, new UpdateAppSettingsByPath(appSettings,new FakeSelectorStorage(storage))); - - var actionResult = await controller.UpdateAppSettings(new AppSettingsTransferObject {Verbose = true}) as JsonResult; + var controller = new AppSettingsController(appSettings, + new UpdateAppSettingsByPath(appSettings, new FakeSelectorStorage(storage))); + + var actionResult = + await controller.UpdateAppSettings(new AppSettingsTransferObject { Verbose = true }) + as JsonResult; var result = actionResult?.Value as AppSettings; Assert.IsTrue(result?.Verbose); } - + [TestMethod] public async Task UpdateAppSettings_StorageFolder() { var appSettings = new AppSettings(); - var controller = new AppSettingsController(appSettings, new UpdateAppSettingsByPath(appSettings, - new FakeSelectorStorage(new FakeIStorage(new List{ $"{Path.DirectorySeparatorChar}test"})))); - + var controller = new AppSettingsController(appSettings, new UpdateAppSettingsByPath( + appSettings, + new FakeSelectorStorage( + new FakeIStorage(new List { $"{Path.DirectorySeparatorChar}test" })))); + controller.ControllerContext.HttpContext = new DefaultHttpContext(); var actionResult = await controller.UpdateAppSettings(new AppSettingsTransferObject { - Verbose = true, - StorageFolder = $"{Path.DirectorySeparatorChar}test" + Verbose = true, StorageFolder = $"{Path.DirectorySeparatorChar}test" }) as JsonResult; var result = actionResult?.Value as AppSettings; Assert.IsTrue(result?.Verbose); - Assert.AreEqual(Path.DirectorySeparatorChar + PathHelper.AddBackslash("test"),result?.StorageFolder); + Assert.AreEqual(Path.DirectorySeparatorChar + PathHelper.AddBackslash("test"), + result?.StorageFolder); } [TestMethod] @@ -81,99 +86,99 @@ public async Task UpdateAppSettingsTest_IgnoreWhenEnvIsSet() "any_value"); var appSettings = new AppSettings(); - var controller = new AppSettingsController(appSettings, new FakeIUpdateAppSettingsByPath(new UpdateAppSettingsStatusModel - { - StatusCode = 403 - })); + var controller = new AppSettingsController(appSettings, + new FakeIUpdateAppSettingsByPath( + new UpdateAppSettingsStatusModel { StatusCode = 403 })); controller.ControllerContext.HttpContext = new DefaultHttpContext(); await controller.UpdateAppSettings( - new AppSettingsTransferObject - { - StorageFolder = "test" - }); + new AppSettingsTransferObject { StorageFolder = "test" }); Assert.AreEqual(403, controller.Response.StatusCode); } - + [TestMethod] public async Task UpdateAppSettingsTest_DirNotFound() { var appSettings = new AppSettings(); - var controller = new AppSettingsController(appSettings, new FakeIUpdateAppSettingsByPath(new UpdateAppSettingsStatusModel - { - StatusCode = 404 - })); + var controller = new AppSettingsController(appSettings, + new FakeIUpdateAppSettingsByPath( + new UpdateAppSettingsStatusModel { StatusCode = 404 })); controller.ControllerContext.HttpContext = new DefaultHttpContext(); - + await controller.UpdateAppSettings( - new AppSettingsTransferObject - { - StorageFolder = "not_found" - }); + new AppSettingsTransferObject { StorageFolder = "not_found" }); Assert.AreEqual(404, controller.Response.StatusCode); } - + [TestMethod] public async Task UpdateAppSettingsTest_StorageFolder_JsonCheck() { var storage = new FakeIStorage(new List { "test" }); Environment.SetEnvironmentVariable("app__storageFolder", string.Empty); - + var appSettings = new AppSettings { - AppSettingsPath = $"{Path.DirectorySeparatorChar}temp{Path.DirectorySeparatorChar}appsettings.json" + AppSettingsPath = + $"{Path.DirectorySeparatorChar}temp{Path.DirectorySeparatorChar}appsettings.json" }; - var controller = new AppSettingsController(appSettings, new UpdateAppSettingsByPath(appSettings,new FakeSelectorStorage(storage))); + var controller = new AppSettingsController(appSettings, + new UpdateAppSettingsByPath(appSettings, new FakeSelectorStorage(storage))); await controller.UpdateAppSettings( - new AppSettingsTransferObject - { - Verbose = true, StorageFolder = "test" - }); + new AppSettingsTransferObject { Verbose = true, StorageFolder = "test" }); Assert.IsTrue(storage.ExistFile(appSettings.AppSettingsPath)); - var jsonContent= await StreamToStringHelper.StreamToStringAsync( + var jsonContent = await StreamToStringHelper.StreamToStringAsync( storage.ReadStream(appSettings.AppSettingsPath)); Assert.IsTrue(jsonContent.Contains("app\": {")); Assert.IsTrue(jsonContent.Contains("\"StorageFolder\": \"")); } - + [TestMethod] public async Task UpdateAppSettings_UseLocalDesktopUi() { var appSettings = new AppSettings(); var storage = new FakeIStorage(new List { "/" }); - var controller = new AppSettingsController(appSettings, new UpdateAppSettingsByPath(appSettings,new FakeSelectorStorage(storage))); + var controller = new AppSettingsController(appSettings, + new UpdateAppSettingsByPath(appSettings, new FakeSelectorStorage(storage))); - var actionResult = await controller.UpdateAppSettings(new AppSettingsTransferObject {UseLocalDesktopUi = true}) as JsonResult; + var actionResult = + await controller.UpdateAppSettings( + new AppSettingsTransferObject { UseLocalDesktopUi = true }) as JsonResult; var result = actionResult?.Value as AppSettings; - Assert.IsTrue(result?.UseLocalDesktopUi); + Assert.IsTrue(result?.UseLocalDesktop); } - + [TestMethod] public async Task UpdateAppSettings_UseSystemTrash() { var appSettings = new AppSettings(); var storage = new FakeIStorage(new List { "/" }); - var controller = new AppSettingsController(appSettings, new UpdateAppSettingsByPath(appSettings,new FakeSelectorStorage(storage))); - - var actionResult = await controller.UpdateAppSettings(new AppSettingsTransferObject {UseSystemTrash = true}) as JsonResult; + var controller = new AppSettingsController(appSettings, + new UpdateAppSettingsByPath(appSettings, new FakeSelectorStorage(storage))); + + var actionResult = + await controller.UpdateAppSettings( + new AppSettingsTransferObject { UseSystemTrash = true }) as JsonResult; var result = actionResult?.Value as AppSettings; Assert.IsTrue(result?.UseSystemTrash); } - + [TestMethod] public async Task UpdateAppSettings_Verbose_IgnoreSystemTrashValue() { var appSettings = new AppSettings(); var storage = new FakeIStorage(new List { "/" }); - var controller = new AppSettingsController(appSettings, new UpdateAppSettingsByPath(appSettings,new FakeSelectorStorage(storage))); - - var actionResult = await controller.UpdateAppSettings(new AppSettingsTransferObject {Verbose = true}) as JsonResult; + var controller = new AppSettingsController(appSettings, + new UpdateAppSettingsByPath(appSettings, new FakeSelectorStorage(storage))); + + var actionResult = + await controller.UpdateAppSettings(new AppSettingsTransferObject { Verbose = true }) + as JsonResult; var result = actionResult?.Value as AppSettings; - + Assert.AreEqual(appSettings.UseSystemTrash, result?.UseSystemTrash); } } diff --git a/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs b/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs index 89f2acc6de..d63ec28f7e 100644 --- a/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs +++ b/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs @@ -19,27 +19,24 @@ public void FeaturesViewTest() var fakeIMoveToTrashService = new FakeIMoveToTrashService(new List()); var appSettingsFeaturesController = new AppSettingsFeaturesController( fakeIMoveToTrashService, new AppSettings()); - + // Act var result = appSettingsFeaturesController.FeaturesView() as JsonResult; var json = result?.Value as EnvFeaturesViewModel; Assert.IsNotNull(json); - + // Assert Assert.IsNotNull(result); } - + [TestMethod] public void FeaturesViewTest_Disabled() { // Arrange var fakeIMoveToTrashService = new FakeIMoveToTrashService(new List(), false); var appSettingsFeaturesController = new AppSettingsFeaturesController( - fakeIMoveToTrashService, new AppSettings - { - UseLocalDesktopUi = false - }); - + fakeIMoveToTrashService, new AppSettings { UseLocalDesktop = false }); + // Act var result = appSettingsFeaturesController.FeaturesView() as JsonResult; var json = result?.Value as EnvFeaturesViewModel; @@ -49,18 +46,15 @@ public void FeaturesViewTest_Disabled() Assert.IsFalse(json.UseLocalDesktopUi); Assert.IsFalse(json.SystemTrashEnabled); } - + [TestMethod] public void FeaturesViewTest_Enabled() { // Arrange var fakeIMoveToTrashService = new FakeIMoveToTrashService(new List()); var appSettingsFeaturesController = new AppSettingsFeaturesController( - fakeIMoveToTrashService, new AppSettings - { - UseLocalDesktopUi = true - }); - + fakeIMoveToTrashService, new AppSettings { UseLocalDesktop = true }); + // Act var result = appSettingsFeaturesController.FeaturesView() as JsonResult; var json = result?.Value as EnvFeaturesViewModel; diff --git a/starsky/starskytest/starsky.foundation.platform/Helpers/AppSettingsCompareHelperTest.cs b/starsky/starskytest/starsky.foundation.platform/Helpers/AppSettingsCompareHelperTest.cs index ca27847264..907793d4e1 100644 --- a/starsky/starskytest/starsky.foundation.platform/Helpers/AppSettingsCompareHelperTest.cs +++ b/starsky/starskytest/starsky.foundation.platform/Helpers/AppSettingsCompareHelperTest.cs @@ -17,7 +17,7 @@ public void NewObject() Assert.AreEqual(input.Structure, new AppSettings().Structure); } - + [TestMethod] public void StringCompare() { @@ -26,7 +26,7 @@ public void StringCompare() DatabaseType = AppSettings.DatabaseTypeList.Sqlite, DatabaseConnection = "Data Source=source" }; - + var to = new AppSettings { DatabaseType = AppSettings.DatabaseTypeList.Sqlite, @@ -36,15 +36,12 @@ public void StringCompare() AppSettingsCompareHelper.Compare(source, to); Assert.AreEqual(source.DatabaseConnection, to.DatabaseConnection); } - + [TestMethod] public void NullableBoolCompare() { - var source = new AppSettings - { - Verbose = true - }; - + var source = new AppSettings { Verbose = true }; + var to = new AppSettingsTransferObject() { Verbose = false // or null @@ -53,51 +50,38 @@ public void NullableBoolCompare() AppSettingsCompareHelper.Compare(source, to); Assert.AreEqual(source.Verbose, to.Verbose); } - + [TestMethod] public void ListStringCompare() { - var source = new AppSettings - { - ReadOnlyFolders = new List{"/test"} - }; - - var to = new AppSettings - { - ReadOnlyFolders = new List{"/test2"} - }; + var source = new AppSettings { ReadOnlyFolders = new List { "/test" } }; + + var to = new AppSettings { ReadOnlyFolders = new List { "/test2" } }; AppSettingsCompareHelper.Compare(source, to); - Assert.AreEqual(source.ReadOnlyFolders.FirstOrDefault(), to.ReadOnlyFolders.FirstOrDefault()); + Assert.AreEqual(source.ReadOnlyFolders.FirstOrDefault(), + to.ReadOnlyFolders.FirstOrDefault()); } - + [TestMethod] public void ListStringCompare_Same() { - var source = new AppSettings - { - ReadOnlyFolders = new List{"/same"} - }; + var source = new AppSettings { ReadOnlyFolders = new List { "/same" } }; - var to = new AppSettings - { - ReadOnlyFolders = new List{"/same"} - }; + var to = new AppSettings { ReadOnlyFolders = new List { "/same" } }; var compare = AppSettingsCompareHelper.Compare(source, source); - - Assert.AreEqual(source.ReadOnlyFolders.FirstOrDefault(), to.ReadOnlyFolders.FirstOrDefault()); + + Assert.AreEqual(source.ReadOnlyFolders.FirstOrDefault(), + to.ReadOnlyFolders.FirstOrDefault()); Assert.AreEqual(0, compare.Count); } - + [TestMethod] public void DatabaseTypeListCompare() { - var source = new AppSettings - { - DatabaseType = AppSettings.DatabaseTypeList.Sqlite - }; - + var source = new AppSettings { DatabaseType = AppSettings.DatabaseTypeList.Sqlite }; + var to = new AppSettings { DatabaseType = AppSettings.DatabaseTypeList.InMemoryDatabase @@ -106,7 +90,7 @@ public void DatabaseTypeListCompare() AppSettingsCompareHelper.Compare(source, to); Assert.AreEqual(source.DatabaseType, to.DatabaseType); } - + [TestMethod] public void ListAppSettingsPublishProfilesCompare() { @@ -114,46 +98,52 @@ public void ListAppSettingsPublishProfilesCompare() { PublishProfiles = new Dictionary> { - {"zz__example", new List { - new AppSettingsPublishProfiles + "zz__example", new List { - ContentType = TemplateContentType.Jpeg, - SourceMaxWidth = 1000, - OverlayMaxWidth = 380, - Path = "{AssemblyDirectory}/EmbeddedViews/qdrawlarge.png", - Folder = "1000", - Append = "_kl1k" + new AppSettingsPublishProfiles + { + ContentType = TemplateContentType.Jpeg, + SourceMaxWidth = 1000, + OverlayMaxWidth = 380, + Path = + "{AssemblyDirectory}/EmbeddedViews/qdrawlarge.png", + Folder = "1000", + Append = "_kl1k" + } } - }} + } } }; - + var to = new AppSettings { PublishProfiles = new Dictionary> { - {"zz__example2", new List { - new AppSettingsPublishProfiles + "zz__example2", + new List { - ContentType = TemplateContentType.Jpeg, - SourceMaxWidth = 300, - OverlayMaxWidth = 380, - Folder = "1000", - Append = "_kl1k" + new AppSettingsPublishProfiles + { + ContentType = TemplateContentType.Jpeg, + SourceMaxWidth = 300, + OverlayMaxWidth = 380, + Folder = "1000", + Append = "_kl1k" + } } - }} + } } }; var compare = AppSettingsCompareHelper.Compare(source, to); - - Assert.AreEqual(source.PublishProfiles.Keys.FirstOrDefault(), to.PublishProfiles.Keys.FirstOrDefault()); - Assert.AreEqual("PublishProfiles".ToLowerInvariant(), compare.FirstOrDefault()); + Assert.AreEqual(source.PublishProfiles.Keys.FirstOrDefault(), + to.PublishProfiles.Keys.FirstOrDefault()); + Assert.AreEqual("PublishProfiles".ToLowerInvariant(), compare.FirstOrDefault()); } - + [TestMethod] public void ListAppSettingsStringDictionary_Changed() { @@ -161,24 +151,26 @@ public void ListAppSettingsStringDictionary_Changed() { AccountRolesByEmailRegisterOverwrite = new Dictionary { - {"zz__example2", "Administrator" - }} + { "zz__example2", "Administrator" } + } }; - + var to = new AppSettings { AccountRolesByEmailRegisterOverwrite = new Dictionary { - {"zz__example2", "User" - }} + { "zz__example2", "User" } + } }; var compare = AppSettingsCompareHelper.Compare(source, to); - - Assert.AreEqual(source.AccountRolesByEmailRegisterOverwrite.Keys.FirstOrDefault(), to.AccountRolesByEmailRegisterOverwrite.Keys.FirstOrDefault()); - Assert.AreEqual("AccountRolesByEmailRegisterOverwrite".ToLowerInvariant(), compare.FirstOrDefault()); + + Assert.AreEqual(source.AccountRolesByEmailRegisterOverwrite.Keys.FirstOrDefault(), + to.AccountRolesByEmailRegisterOverwrite.Keys.FirstOrDefault()); + Assert.AreEqual("AccountRolesByEmailRegisterOverwrite".ToLowerInvariant(), + compare.FirstOrDefault()); } - + [TestMethod] public void ListAppSettingsStringDictionary_Equal() { @@ -186,21 +178,23 @@ public void ListAppSettingsStringDictionary_Equal() { AccountRolesByEmailRegisterOverwrite = new Dictionary { - {"zz__example2", "Administrator" - }} + { "zz__example2", "Administrator" } + } }; - + var to = new AppSettings { - AccountRolesByEmailRegisterOverwrite = source.AccountRolesByEmailRegisterOverwrite + AccountRolesByEmailRegisterOverwrite = + source.AccountRolesByEmailRegisterOverwrite }; var compare = AppSettingsCompareHelper.Compare(source, to); - - Assert.AreEqual(source.AccountRolesByEmailRegisterOverwrite.Keys.FirstOrDefault(), to.AccountRolesByEmailRegisterOverwrite.Keys.FirstOrDefault()); + var expected = source.AccountRolesByEmailRegisterOverwrite.Keys.FirstOrDefault(); + var actual = to.AccountRolesByEmailRegisterOverwrite.Keys.FirstOrDefault(); + Assert.AreEqual(expected, actual); Assert.AreEqual(0, compare.Count); } - + [TestMethod] public void ListAppSettingsStringDictionary_IgnoreOverwrite() { @@ -208,20 +202,18 @@ public void ListAppSettingsStringDictionary_IgnoreOverwrite() { AccountRolesByEmailRegisterOverwrite = new Dictionary { - {"zz__example2", "Administrator" - }} - }; - - var to = new AppSettings - { - AccountRolesByEmailRegisterOverwrite = null + { "zz__example2", "Administrator" } + } }; + var to = new AppSettings { AccountRolesByEmailRegisterOverwrite = null }; + AppSettingsCompareHelper.Compare(source, to); - - Assert.AreEqual(null, to.AccountRolesByEmailRegisterOverwrite?.Keys.FirstOrDefault()); + + var actual = to.AccountRolesByEmailRegisterOverwrite?.Keys.FirstOrDefault(); + Assert.IsNull(actual); } - + [TestMethod] public void KeyValuePairStringString_Changed() { @@ -229,34 +221,28 @@ public void KeyValuePairStringString_Changed() { DemoData = new List { - new AppSettingsKeyValue - { - Key = "1", - Value = "2" - } + new AppSettingsKeyValue { Key = "1", Value = "2" } } }; - + var to = new AppSettings { DemoData = new List { - new AppSettingsKeyValue - { - Key = "3", - Value = "4" - } + new AppSettingsKeyValue { Key = "3", Value = "4" } } }; var compare = AppSettingsCompareHelper.Compare(source, to); - - Assert.AreEqual(source.DemoData.FirstOrDefault()?.Key, to.DemoData.FirstOrDefault()?.Key); - Assert.AreEqual(source.DemoData.FirstOrDefault()?.Value, to.DemoData.FirstOrDefault()?.Value); + + Assert.AreEqual(source.DemoData.FirstOrDefault()?.Key, + to.DemoData.FirstOrDefault()?.Key); + Assert.AreEqual(source.DemoData.FirstOrDefault()?.Value, + to.DemoData.FirstOrDefault()?.Value); Assert.AreEqual("DemoData".ToLowerInvariant(), compare.FirstOrDefault()); } - + [TestMethod] public void KeyValuePairStringString_Equal() { @@ -264,26 +250,21 @@ public void KeyValuePairStringString_Equal() { DemoData = new List { - new AppSettingsKeyValue - { - Key = "1", - Value = "2" - } + new AppSettingsKeyValue { Key = "1", Value = "2" } } }; - - var to = new AppSettings - { - DemoData = source.DemoData - }; + + var to = new AppSettings { DemoData = source.DemoData }; var compare = AppSettingsCompareHelper.Compare(source, to); - Assert.AreEqual(source.DemoData.FirstOrDefault()?.Key, to.DemoData.FirstOrDefault()?.Key); - Assert.AreEqual(source.DemoData.FirstOrDefault()?.Value, to.DemoData.FirstOrDefault()?.Value); + Assert.AreEqual(source.DemoData.FirstOrDefault()?.Key, + to.DemoData.FirstOrDefault()?.Key); + Assert.AreEqual(source.DemoData.FirstOrDefault()?.Value, + to.DemoData.FirstOrDefault()?.Value); Assert.AreEqual(0, compare.Count); } - + [TestMethod] public void KeyValuePairStringString_IgnoreOverwrite() { @@ -291,26 +272,19 @@ public void KeyValuePairStringString_IgnoreOverwrite() { DemoData = new List { - new AppSettingsKeyValue - { - Key = "1", - Value = "2" - } + new AppSettingsKeyValue { Key = "1", Value = "2" } } }; - - var to = new AppSettings - { - DemoData = null! - }; + + var to = new AppSettings { DemoData = null! }; var compare = AppSettingsCompareHelper.Compare(source, to); - + Assert.IsNull(to.DemoData); Assert.AreEqual(0, compare.Count); } - + [TestMethod] public void AppSettingsKeyValue_Compare() { @@ -318,32 +292,24 @@ public void AppSettingsKeyValue_Compare() { DemoData = new List { - new AppSettingsKeyValue - { - Key = "2", - Value = "1" - } + new AppSettingsKeyValue { Key = "2", Value = "1" } } }; - + var to = new AppSettings { DemoData = new List { - new AppSettingsKeyValue - { - Key = "1", - Value = "1" - } + new AppSettingsKeyValue { Key = "1", Value = "1" } } }; AppSettingsCompareHelper.Compare(source, to); - - Assert.AreEqual(source.PublishProfiles?.Keys.FirstOrDefault(), + + Assert.AreEqual(source.PublishProfiles?.Keys.FirstOrDefault(), to.PublishProfiles?.Keys.FirstOrDefault()); } - + [TestMethod] public void AppSettingsKeyValue_Compare_Same() { @@ -351,25 +317,18 @@ public void AppSettingsKeyValue_Compare_Same() { DemoData = new List { - new AppSettingsKeyValue - { - Key = "same", - Value = "1" - } + new AppSettingsKeyValue { Key = "same", Value = "1" } } }; - - var to = new AppSettings - { - DemoData = source.DemoData - }; + + var to = new AppSettings { DemoData = source.DemoData }; AppSettingsCompareHelper.Compare(source, to); - - Assert.AreEqual(source.PublishProfiles?.Keys.FirstOrDefault(), + + Assert.AreEqual(source.PublishProfiles?.Keys.FirstOrDefault(), to.PublishProfiles?.Keys.FirstOrDefault()); } - + [TestMethod] public void ListAppSettingsPublishProfilesCompare_Same() { @@ -377,40 +336,47 @@ public void ListAppSettingsPublishProfilesCompare_Same() { PublishProfiles = new Dictionary> { - {"same", new List { - new AppSettingsPublishProfiles + "same", + new List { - ContentType = TemplateContentType.Jpeg, - SourceMaxWidth = 300, - OverlayMaxWidth = 380, - Folder = "1000", - Append = "_kl1k" + new AppSettingsPublishProfiles + { + ContentType = TemplateContentType.Jpeg, + SourceMaxWidth = 300, + OverlayMaxWidth = 380, + Folder = "1000", + Append = "_kl1k" + } } - }} + } } }; - + var to = new AppSettings { PublishProfiles = new Dictionary> { - {"same", new List { - new AppSettingsPublishProfiles + "same", + new List { - ContentType = TemplateContentType.Jpeg, - SourceMaxWidth = 300, - OverlayMaxWidth = 380, - Folder = "1000", - Append = "_kl1k" + new AppSettingsPublishProfiles + { + ContentType = TemplateContentType.Jpeg, + SourceMaxWidth = 300, + OverlayMaxWidth = 380, + Folder = "1000", + Append = "_kl1k" + } } - }} + } } }; AppSettingsCompareHelper.Compare(source, to); - Assert.AreEqual(source.PublishProfiles.Keys.FirstOrDefault(), to.PublishProfiles.Keys.FirstOrDefault()); + Assert.AreEqual(source.PublishProfiles.Keys.FirstOrDefault(), + to.PublishProfiles.Keys.FirstOrDefault()); } [TestMethod] @@ -423,18 +389,18 @@ public void CompareDatabaseTypeList_NotFound() AppSettings.DatabaseTypeList.Mysql, list); Assert.IsNotNull(list); } - + [TestMethod] public void CompareListString_NotFound() { var list = new List(); AppSettingsCompareHelper.CompareListString("t", new AppSettings(), - new List{"1"}, - new List{"1"}, list); + new List { "1" }, + new List { "1" }, list); Assert.IsNotNull(list); } - + [TestMethod] public void CompareListPublishProfiles_NotFound() { @@ -445,7 +411,7 @@ public void CompareListPublishProfiles_NotFound() new Dictionary>(), list); Assert.IsNotNull(list); } - + [TestMethod] public void CompareBool_NotFound() { @@ -457,7 +423,7 @@ public void CompareBool_NotFound() boolValue, list); Assert.IsNotNull(list); } - + [TestMethod] public void CompareString_NotFound() { @@ -468,7 +434,7 @@ public void CompareString_NotFound() "test", list); Assert.IsNotNull(list); } - + [TestMethod] public void CompareInt_NotFound() { @@ -479,7 +445,7 @@ public void CompareInt_NotFound() 2, list); Assert.IsNotNull(list); } - + [TestMethod] public void OpenTelemetrySettings() { @@ -496,7 +462,7 @@ public void OpenTelemetrySettings() LogsHeader = "source/logs" } }; - + var to = new AppSettings { OpenTelemetry = new OpenTelemetrySettings @@ -512,7 +478,7 @@ public void OpenTelemetrySettings() }; AppSettingsCompareHelper.Compare(source, to); - + Assert.AreEqual(source.OpenTelemetry.Header, to.OpenTelemetry.Header); Assert.AreEqual(source.OpenTelemetry.TracesEndpoint, to.OpenTelemetry.TracesEndpoint); Assert.AreEqual(source.OpenTelemetry.TracesHeader, to.OpenTelemetry.TracesHeader); @@ -520,7 +486,6 @@ public void OpenTelemetrySettings() Assert.AreEqual(source.OpenTelemetry.MetricsHeader, to.OpenTelemetry.MetricsHeader); Assert.AreEqual(source.OpenTelemetry.LogsEndpoint, to.OpenTelemetry.LogsEndpoint); Assert.AreEqual(source.OpenTelemetry.LogsHeader, to.OpenTelemetry.LogsHeader); - } [TestMethod] @@ -539,18 +504,86 @@ public void OpenTelemetrySettings_Ignore_DefaultOption() LogsHeader = "source/logs" } }; - - var to = new AppSettings - { - OpenTelemetry = new OpenTelemetrySettings() - }; + + var to = new AppSettings { OpenTelemetry = new OpenTelemetrySettings() }; AppSettingsCompareHelper.Compare(source, to); - + Assert.AreEqual("source/test", source.OpenTelemetry.Header); Assert.AreEqual("source/traces", source.OpenTelemetry.TracesEndpoint); Assert.AreEqual("source/metrics", source.OpenTelemetry.MetricsEndpoint); Assert.AreEqual("source/logs", source.OpenTelemetry.LogsEndpoint); } + + [TestMethod] + public void AppSettingsDefaultEditorApplication() + { + var source = new AppSettings + { + DefaultDesktopEditor = + [ + new AppSettingsDefaultEditorApplication() + { + ImageFormats = + [ + ExtensionRolesHelper.ImageFormat.bmp, + ExtensionRolesHelper.ImageFormat.jpg + ], + ApplicationPath = "source/test" + } + ] + }; + + var to = new AppSettings + { + DefaultDesktopEditor = + [ + new AppSettingsDefaultEditorApplication() + { + ImageFormats = [ExtensionRolesHelper.ImageFormat.jpg], + ApplicationPath = "to/test" + } + ] + }; + + AppSettingsCompareHelper.Compare(source, to); + + Assert.AreEqual(source.DefaultDesktopEditor.Count, to.DefaultDesktopEditor.Count); + Assert.AreEqual(source.DefaultDesktopEditor[0].ApplicationPath, + to.DefaultDesktopEditor[0].ApplicationPath); + Assert.AreEqual(source.DefaultDesktopEditor[0].ImageFormats, + to.DefaultDesktopEditor[0].ImageFormats); + } + + [TestMethod] + public void AppSettingsDefaultEditorApplication_Ignore_DefaultOption() + { + var source = new AppSettings + { + DefaultDesktopEditor = + [ + new AppSettingsDefaultEditorApplication() + { + ImageFormats = + [ + ExtensionRolesHelper.ImageFormat.bmp, + ExtensionRolesHelper.ImageFormat.jpg + ], + ApplicationPath = "source/test" + } + ] + }; + + var to = new AppSettings { DefaultDesktopEditor = [] }; + + AppSettingsCompareHelper.Compare(source, to); + + Assert.AreEqual(source.DefaultDesktopEditor.Count, to.DefaultDesktopEditor.Count); + Assert.AreEqual("source/test", source.DefaultDesktopEditor[0].ApplicationPath); + Assert.AreEqual([ + ExtensionRolesHelper.ImageFormat.bmp, + ExtensionRolesHelper.ImageFormat.jpg + ], source.DefaultDesktopEditor[0].ImageFormats); + } } } From a12c496e3c17cddf309983acc51266abe39448c2 Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 15 Feb 2024 20:48:59 +0100 Subject: [PATCH 031/125] test this --- .../Service/OpenEditorDesktopService.cs | 11 +- .../Helpers/AppSettingsCompareHelper.cs | 41 +++- .../Helpers/Compare/AreListsEqual.cs | 24 +++ .../JsonConverter/EnumListConverter.cs | 54 ++++++ .../AppSettingsDefaultEditorApplication.cs | 6 +- .../Models/AppSettingsTransferObject.cs | 2 +- .../AppSettingsFeaturesController.cs | 2 +- starsky/starsky/appsettings.json | 12 +- starsky/starsky/readme.md | 177 ++++++++++++------ .../ViewModels/EnvFeaturesViewModel.cs | 3 +- .../Controllers/AppSettingsControllerTest.cs | 4 +- .../AppSettingsFeaturesControllerTest.cs | 4 +- .../Services/UpdateAppSettingsByPathTest.cs | 4 +- .../Helpers/AppSettingsCompareHelperTest.cs | 7 +- .../JsonConverter/EnumListConverterTest.cs | 88 +++++++++ ...AppSettingsDefaultEditorApplicationTest.cs | 47 +++++ 16 files changed, 405 insertions(+), 81 deletions(-) create mode 100644 starsky/starsky.foundation.platform/Helpers/Compare/AreListsEqual.cs create mode 100644 starsky/starsky.foundation.platform/JsonConverter/EnumListConverter.cs create mode 100644 starsky/starskytest/starsky.foundation.platform/JsonConverter/EnumListConverterTest.cs create mode 100644 starsky/starskytest/starsky.foundation.platform/Models/AppSettingsDefaultEditorApplicationTest.cs diff --git a/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs b/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs index b18c6983dc..bd4a195f28 100644 --- a/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs +++ b/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs @@ -18,8 +18,15 @@ public OpenEditorDesktopService(AppSettings appSettings, _openApplicationNativeService = openApplicationNativeService; } - public void Test() + public void Open(string subPath) { - _appSettings.UseLocalDesktop + if ( _appSettings.UseLocalDesktop == false ) + { + return; + } + + // // + // _openApplicationNativeService.Open(subPath); } + } diff --git a/starsky/starsky.foundation.platform/Helpers/AppSettingsCompareHelper.cs b/starsky/starsky.foundation.platform/Helpers/AppSettingsCompareHelper.cs index 936ff1517c..d41d23a430 100644 --- a/starsky/starsky.foundation.platform/Helpers/AppSettingsCompareHelper.cs +++ b/starsky/starsky.foundation.platform/Helpers/AppSettingsCompareHelper.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.Json; +using starsky.foundation.platform.Helpers.Compare; using starsky.foundation.platform.JsonConverter; using starsky.foundation.platform.Models; @@ -36,14 +37,15 @@ public static List Compare(AppSettings sourceIndexItem, object? updateOb updateObject, differenceList); CompareMultipleListDictionary(propertyB, propertyInfoFromA, sourceIndexItem, updateObject, differenceList); - CompareMultipleObjects(propertyB, propertyInfoFromA, sourceIndexItem, updateObject, + CompareListMultipleObjects(propertyB, propertyInfoFromA, sourceIndexItem, + updateObject, differenceList); } return differenceList; } - private static void CompareMultipleObjects(PropertyInfo propertyB, + private static void CompareListMultipleObjects(PropertyInfo propertyB, PropertyInfo propertyInfoFromA, AppSettings sourceIndexItem, object updateObject, List differenceList) { @@ -57,6 +59,21 @@ private static void CompareMultipleObjects(PropertyInfo propertyB, CompareOpenTelemetrySettingsObject(propertyB.Name, sourceIndexItem, oldObjectValue, newObjectValue, differenceList); } + + if ( propertyInfoFromA.PropertyType == + typeof(List) && + propertyB.PropertyType == typeof(List) ) + { + var oldObjectValue = + ( List? )propertyInfoFromA.GetValue( + sourceIndexItem, null); + var newObjectValue = + ( List? )propertyB.GetValue(updateObject, + null); + CompareAppSettingsDefaultEditorApplication(propertyB.Name, sourceIndexItem, + oldObjectValue, + newObjectValue, differenceList); + } } [SuppressMessage("Performance", @@ -84,6 +101,26 @@ private static void CompareOpenTelemetrySettingsObject(string propertyName, differenceList.Add(propertyName.ToLowerInvariant()); } + private static void CompareAppSettingsDefaultEditorApplication(string propertyName, + AppSettings? sourceIndexItem, + List? oldKeyValuePairStringStringValue, + List? newKeyValuePairStringStringValue, + List differenceList) + { + if ( oldKeyValuePairStringStringValue == null || + newKeyValuePairStringStringValue == null || + newKeyValuePairStringStringValue.Count == 0 || + AreListsEqualHelper.AreListsEqual(oldKeyValuePairStringStringValue, + newKeyValuePairStringStringValue) ) + { + return; + } + + sourceIndexItem?.GetType().GetProperty(propertyName)?.SetValue(sourceIndexItem, + newKeyValuePairStringStringValue, null); + differenceList.Add(propertyName.ToLowerInvariant()); + } + private static void CompareMultipleSingleItems(PropertyInfo propertyB, PropertyInfo propertyInfoFromA, AppSettings sourceIndexItem, object updateObject, diff --git a/starsky/starsky.foundation.platform/Helpers/Compare/AreListsEqual.cs b/starsky/starsky.foundation.platform/Helpers/Compare/AreListsEqual.cs new file mode 100644 index 0000000000..54edff6ea8 --- /dev/null +++ b/starsky/starsky.foundation.platform/Helpers/Compare/AreListsEqual.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace starsky.foundation.platform.Helpers.Compare; + +public static class AreListsEqualHelper +{ + internal static bool AreListsEqual(List list1, List list2) + { + if ( list1.Count != list2.Count ) + { + return false; + } + + for ( var i = 0; i < list1.Count; i++ ) + { + if ( list1[i]?.Equals(list2[i]) == false ) + { + return false; + } + } + + return true; + } +} diff --git a/starsky/starsky.foundation.platform/JsonConverter/EnumListConverter.cs b/starsky/starsky.foundation.platform/JsonConverter/EnumListConverter.cs new file mode 100644 index 0000000000..57632d1a7e --- /dev/null +++ b/starsky/starsky.foundation.platform/JsonConverter/EnumListConverter.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace starsky.foundation.platform.JsonConverter; + +/// +/// Enum converter for Lists with Enum into Json +/// +/// Enum +public class EnumListConverter : JsonConverter> where T : struct, Enum +{ + public override List Read(ref Utf8JsonReader reader, Type typeToConvert, + JsonSerializerOptions options) + { + if ( reader.TokenType != JsonTokenType.StartArray ) + throw new JsonException(); + + var result = new List(); + + while ( reader.Read() ) + { + if ( reader.TokenType == JsonTokenType.EndArray ) + return result; + + if ( reader.TokenType != JsonTokenType.String ) + throw new JsonException(); + + if ( Enum.TryParse(reader.GetString(), out var enumValue) ) + { + result.Add(enumValue); + } + else + { + throw new JsonException($"Unknown enum value: {reader.GetString()}"); + } + } + + throw new JsonException("Unexpected end of JSON input"); + } + + public override void Write(Utf8JsonWriter writer, List value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + + foreach ( var item in value ) + { + writer.WriteStringValue(item.ToString()); + } + + writer.WriteEndArray(); + } +} diff --git a/starsky/starsky.foundation.platform/Models/AppSettingsDefaultEditorApplication.cs b/starsky/starsky.foundation.platform/Models/AppSettingsDefaultEditorApplication.cs index ca243091d7..6ca0df653f 100644 --- a/starsky/starsky.foundation.platform/Models/AppSettingsDefaultEditorApplication.cs +++ b/starsky/starsky.foundation.platform/Models/AppSettingsDefaultEditorApplication.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Text.Json.Serialization; using starsky.foundation.platform.Helpers; +using starsky.foundation.platform.JsonConverter; namespace starsky.foundation.platform.Models; @@ -9,11 +10,12 @@ public class AppSettingsDefaultEditorApplication /// /// For what type of files /// - [JsonConverter(typeof(JsonStringEnumConverter))] + [JsonConverter(typeof(EnumListConverter))] public List ImageFormats { get; set; } = []; /// /// Path to .exe on windows and .app on Mac OS + /// No check if exists here /// - public string ApplicationPath { get; set; } + public string ApplicationPath { get; set; } = string.Empty; } diff --git a/starsky/starsky.foundation.platform/Models/AppSettingsTransferObject.cs b/starsky/starsky.foundation.platform/Models/AppSettingsTransferObject.cs index 483cc02ddb..970419ffd3 100644 --- a/starsky/starsky.foundation.platform/Models/AppSettingsTransferObject.cs +++ b/starsky/starsky.foundation.platform/Models/AppSettingsTransferObject.cs @@ -10,6 +10,6 @@ public sealed class AppSettingsTransferObject public string? StorageFolder { get; set; } public bool? UseSystemTrash { get; set; } - public bool? UseLocalDesktopUi { get; set; } + public bool? UseLocalDesktop { get; set; } } } diff --git a/starsky/starsky/Controllers/AppSettingsFeaturesController.cs b/starsky/starsky/Controllers/AppSettingsFeaturesController.cs index 91462da8fa..da45e3ec18 100644 --- a/starsky/starsky/Controllers/AppSettingsFeaturesController.cs +++ b/starsky/starsky/Controllers/AppSettingsFeaturesController.cs @@ -37,7 +37,7 @@ public IActionResult FeaturesView() var shortAppSettings = new EnvFeaturesViewModel { SystemTrashEnabled = _moveToTrashService.IsEnabled(), - UseLocalDesktopUi = _appSettings.UseLocalDesktop == true + UseLocalDesktop = _appSettings.UseLocalDesktop == true }; return Json(shortAppSettings); diff --git a/starsky/starsky/appsettings.json b/starsky/starsky/appsettings.json index c8358f69b0..30b2436933 100644 --- a/starsky/starsky/appsettings.json +++ b/starsky/starsky/appsettings.json @@ -57,10 +57,20 @@ "SyncOnStartup": "true", "DemoUnsafeDeleteStorageFolder": "false", "useSystemTrash": "true", + "UseLocalDesktop": "false", + "DefaultDesktopEditor": [ + { + "ApplicationPath": "/Applications/Adobe Photoshop 2024/Adobe Photoshop 2024.app", + "ImageFormats": ["jpg", "bmp", "png", "gif", "tiff"] + } + ], "OpenTelemetry": { "TracesEndpoint": null, "MetricsEndpoint": null, - "LogsEndpoint": null + "LogsEndpoint": null, + "TracesHeader": null, + "MetricsHeader": null, + "LogsHeader": null }, "publishProfiles": { "_default": [ diff --git a/starsky/starsky/readme.md b/starsky/starsky/readme.md index c4e42cbcd8..ebc18f21b6 100644 --- a/starsky/starsky/readme.md +++ b/starsky/starsky/readme.md @@ -1,98 +1,134 @@ # Web API application + ## List of [Starsky](../../readme.md) Projects - * [By App documentation](../../starsky/readme.md) _database photo index & import index project_ + +* [By App documentation](../../starsky/readme.md) _database photo index & import index project_ * __[starsky](../../starsky/starsky/readme.md) web api application / interface__ - * [clientapp](../../starsky/starsky/clientapp/readme.md) _react front-end application_ - * [starskyImporterCli](../../starsky/starskyimportercli/readme.md) _import command line interface_ + * [clientapp](../../starsky/starsky/clientapp/readme.md) _react front-end application_ + * [starskyImporterCli](../../starsky/starskyimportercli/readme.md) _import command line + interface_ * [starskyGeoCli](../../starsky/starskygeocli/readme.md) _gpx sync and reverse 'geo tagging'_ - * [starskyWebHtmlCli](../../starsky/starskywebhtmlcli/readme.md) _publish web images to a content package_ - * [starskyWebFtpCli](../../starsky/starskywebftpcli/readme.md) _copy a content package to a ftp service_ + * [starskyWebHtmlCli](../../starsky/starskywebhtmlcli/readme.md) _publish web images to a + content package_ + * [starskyWebFtpCli](../../starsky/starskywebftpcli/readme.md) _copy a content package to a ftp + service_ * [starskyAdminCli](../../starsky/starskyadmincli/readme.md) _manage user accounts_ - * [starskySynchronizeCli](../../starsky/starskysynchronizecli/readme.md) _check if disk changes are updated in the database_ - * [starskyThumbnailCli](../../starsky/starskythumbnailcli/readme.md) _speed web performance by generating smaller images_ - * [Starsky Business Logic](../../starsky/starskybusinesslogic/readme.md) _business logic libraries (.NET)_ + * [starskySynchronizeCli](../../starsky/starskysynchronizecli/readme.md) _check if disk changes + are updated in the database_ + * [starskyThumbnailCli](../../starsky/starskythumbnailcli/readme.md) _speed web performance by + generating smaller images_ + * [Starsky Business Logic](../../starsky/starskybusinesslogic/readme.md) _business logic + libraries (.NET)_ * [starskyTest](../../starsky/starskytest/readme.md) _mstest unit tests (for .NET)_ - * [starsky-tools](../../starsky-tools/readme.md) _nodejs tools to add-on tasks_ - * [Starsky Desktop](../../starskydesktop/readme.md) _Desktop Application_ +* [starsky-tools](../../starsky-tools/readme.md) _nodejs tools to add-on tasks_ +* [Starsky Desktop](../../starskydesktop/readme.md) _Desktop Application_ * [Download Desktop App](https://docs.qdraw.nl/download/) _Windows and Mac OS version_ - * [Changelog](../../history.md) _Release notes and history_ +* [Changelog](../../history.md) _Release notes and history_ ## starsky/starsky docs ### Structure configuration: When setup Starksy there are two options to configure the installation. -There is a list of required settings. First the `appsettings.json` is loaded and the environment variables are overwriting features. +There is a list of required settings. First the `appsettings.json` is loaded and the environment +variables are overwriting features. The command line arguments are shortcuts to set an in-app environment variable. ### The order of reading settings -You could use machine specific configuration files: appsettings.machinename.json _(and replace machinename with your computer name in lowercase)_ -1. You can use `appsettings.json` inside the application folder to set base settings. - The order of this files is used to get the values from the appsettings - - `/bin/Debug/net8.0/appsettings.patch.json` - - `/bin/Debug/net8.0/appsettings.default.json` - - `/bin/Debug/net8.0/appsettings.computername.patch.json` - - `/bin/Debug/net8.0/appsettings.json` - - `/bin/Debug/net8.0/appsettings.computername.json` - -2. Use Environment variables to overwrite those base settings + +You could use machine specific configuration files: appsettings.machinename.json _(and replace +machinename with your computer name in lowercase)_ + +1. You can use `appsettings.json` inside the application folder to set base settings. + The order of this files is used to get the values from the appsettings + - `/bin/Debug/net8.0/appsettings.patch.json` + - `/bin/Debug/net8.0/appsettings.default.json` + - `/bin/Debug/net8.0/appsettings.computername.patch.json` + - `/bin/Debug/net8.0/appsettings.json` + - `/bin/Debug/net8.0/appsettings.computername.json` + +2. Use Environment variables to overwrite those base settings For `ThumbnailTempFolder` use `app__ThumbnailTempFolder` ([source](https://github.com/aspnet/Configuration/commit/cafd2e53eb71a6d0cecc60a9e38ea1df2dafb916)) - Dictionaries can be used this way: `app__accountRolesByEmailRegisterOverwrite__test@mail.be` -3. Command line arguments in the Cli applications to set in-app environment variables + Dictionaries can be used this way: `app__accountRolesByEmailRegisterOverwrite__test@mail.be` +3. Command line arguments in the Cli applications to set in-app environment variables ### Required settings to start + 1. To start it is __not__ mandatory to adjust any settings. ### Recommend settings -1. `ThumbnailTempFolder` - For storing thumbnails (default: `./bin/Debug/net8.0/thumbnailTempFolder`) -2. `StorageFolder` - For the main photo directory (default: `./bin/Debug/net8.0/storageFolder`) -3. `DatabaseType` - `mysql`, `sqlite` or `inmemorydatabase` are supported (default: `sqlite`) -4. `DatabaseConnection` - The connection-string to the database (default: `./bin/Debug/net8.0/data.db`) -5. `CameraTimeZone` - The timezone of the Camera, for example `Europe/Amsterdam` (defaults to your local timezone) + +1. `ThumbnailTempFolder` - For storing thumbnails ( + default: `./bin/Debug/net8.0/thumbnailTempFolder`) +2. `StorageFolder` - For the main photo directory (default: `./bin/Debug/net8.0/storageFolder`) +3. `DatabaseType` - `mysql`, `sqlite` or `inmemorydatabase` are supported (default: `sqlite`) +4. `DatabaseConnection` - The connection-string to the database ( + default: `./bin/Debug/net8.0/data.db`) +5. `CameraTimeZone` - The timezone of the Camera, for example `Europe/Amsterdam` (defaults to your + local timezone) ### Optional settings + 1. `Structure` - The structure that will be used when you import files, _has a default fallback_. -2. `DependenciesFolder` - where store the data of external dependencies used _default folder in project_ +2. `DependenciesFolder` - where store the data of external dependencies used _default folder in + project_ 3. `ReadOnlyFolders` - Accepts a list of folders that never may be edited, _defaults a empty list_ 4. `AddMemoryCache` - Enable caching _(default true)_ - The only 2 build-in exceptions are when there are no accounts or you already logged in _(default false)_ + The only 2 build-in exceptions are when there are no accounts or you already logged in _(default + false)_ 5. `AddSwagger` - To show a user interface to show al REST-services _(default false)_ -6. `ExifToolImportXmpCreate` - is used to create at import time a xmp file based on the raw image _(default false)_ +6. `ExifToolImportXmpCreate` - is used to create at import time a xmp file based on the raw image _( + default false)_ 7. `AddSwaggerExport` - To Export Swagger definitions on startup _(default false)_ 8. `AddLegacyOverwrite`- Read Only value for ("Mono.Runtime") _(default false)_ 9. `Verbose` - show more console logging _(default false)_ 10. `WebFtp` - ftp path, this is used by starskyWebFtpCli -11. `PublishProfiles` - settings to configure publish output, used by starskyWebHtmlCli and publish button +11. `PublishProfiles` - settings to configure publish output, used by starskyWebHtmlCli and publish + button 12. `ExifToolPath` - A path to Exiftool.exe _to ignore the included ExifTool_ 13. `isAccountRegisterOpen` - Allow everyone to register an account _(default false)_ -14. `AccountRegisterDefaultRole` When a user is new and register an account, give it the role User or Administrator _(default User)_ -15. `useHttpsRedirection` - Redirect users to https page. You should enable before going to production. - This toggle is always disabled in debug/develop mode _(default false)_ -16. `httpsOn` Set all cookies in https Mode. You should enable before going to production. _(default false)_ +14. `AccountRegisterDefaultRole` When a user is new and register an account, give it the role User + or Administrator _(default User)_ +15. `useHttpsRedirection` - Redirect users to https page. You should enable before going to + production. + This toggle is always disabled in debug/develop mode _(default false)_ +16. `httpsOn` Set all cookies in https Mode. You should enable before going to production. _(default + false)_ 17. `Name` Name of the application, does not have much effect _(default Starsky)_ 18. `AppSettingsPath` To store the settings by user in the AppData folder _(default empty string)_ 19. `UseRealtime` Update the user interface realtime _default true_ 20. `UseDiskWatcher` Watch the disk for changes and update the database _default true_ 21. `CheckForUpdates` Check if there are updates on github and notify the user _default true_ -22. `SyncIgnore` Ignore pattern to not include disk items while running sync, uses always unix style and startsWith _default list with: /lost+found_ +22. `SyncIgnore` Ignore pattern to not include disk items while running sync, uses always unix style + and startsWith _default list with: /lost+found_ 23. `ImportIgnore` ImportIgnore filter _default list with: "lost+found" ".Trashes"_ 24. `MaxDegreesOfParallelism` Number of jobs running in background _default 6_ 25. `MetaThumbnailOnImport` Create small thumbnails after import, is very fast _default true_ 26. `EnablePackageTelemetry` Telemetry is send for service improvement _default true_ 27. `EnablePackageTelemetryDebug` Debug Telemetry _default false_ -28. `AddSwaggerExportExitAfter` Quit application after exporting swagger files, should have `AddSwagger` and `AddSwaggerExport` enabled _default false_ +28. `AddSwaggerExportExitAfter` Quit application after exporting swagger files, should + have `AddSwagger` and `AddSwaggerExport` enabled _default false_ 29. `NoAccountLocalhost` No login needed when on localhost, used in Desktop App 30. `VideoUseLocalTime` Use localtime by Camera make and model instead of UTC 31. `SyncOnStartup` Sync Database on changes since latest start _default true_ -32. `ThumbnailGenerationIntervalInMinutes` Interval to generate thumbnails, to disable use value lower than 3 _default 15_ -33. `GeoFilesSkipDownloadOnStartup` Skip download of GeoFiles on startup, _recommend to keep this false or null_ - _default false_ -34. `ExiftoolSkipDownloadOnStartup` Skip download of Exiftool on startup, _recommend to keep this false or null_ - _default false_ -35. `AccountRolesByEmailRegisterOverwrite` Overwrite the default role for a user by email address, _default empty list_ -36. `OpenTelemetry` See logging in an external service, _default no enabled_ see [OpenTelemetry](https://docs.qdraw.nl/docs/developer-guide/logging/opentelemetry.md) - +32. `ThumbnailGenerationIntervalInMinutes` Interval to generate thumbnails, to disable use value + lower than 3 _default 15_ +33. `GeoFilesSkipDownloadOnStartup` Skip download of GeoFiles on startup, _recommend to keep this + false or null_ - _default false_ +34. `ExiftoolSkipDownloadOnStartup` Skip download of Exiftool on startup, _recommend to keep this + false or null_ - _default false_ +35. `AccountRolesByEmailRegisterOverwrite` Overwrite the default role for a user by email address, + _default empty list_ +36. `OpenTelemetry` See logging in an external service, _default no enabled_ + see [OpenTelemetry](https://docs.qdraw.nl/docs/developer-guide/logging/opentelemetry.md) +37. `UseLocalDesktop` Enable local desktop features (hide trash in Ui / Open in Application) + _default false_ _in app true_ +38. `DefaultDesktopEditor` List of Properties that contain the default editor by imageFormat + _default none_ ### Appsettings.json example + ```json { "App": { @@ -111,18 +147,24 @@ You could use machine specific configuration files: appsettings.machinename.json > Note: When using a boolean in the json add quotes. Booleans without quotes are ignored -> Tip: When using the `mysql`-setting, make sure the database uses `utf8mb4` and as collate `utf8mb4_unicode_ci` to avoid encoding errors. +> Tip: When using the `mysql`-setting, make sure the database uses `utf8mb4` and as +> collate `utf8mb4_unicode_ci` to avoid encoding errors. #### Appsettings Notes -1. Structure uses slash as directory separators for Linux and Windows -2. The settings: `ExifToolPath`, `ThumbnailTempFolder` and `StorageFolder` uses the system path directory separators -3. When using Windows please double escape (`\\`) system path's + +1. Structure uses slash as directory separators for Linux and Windows +2. The settings: `ExifToolPath`, `ThumbnailTempFolder` and `StorageFolder` uses the system path + directory separators +3. When using Windows please double escape (`\\`) system path's ### Warmup script + The default behavior of .NET is to load everything first. -To be sure that the application is warm before someone arrives, please check `tools/starsky-warmup.sh`. +To be sure that the application is warm before someone arrives, please +check `tools/starsky-warmup.sh`. ### Search Docs + Advanced queries are supported by the basic search engine. __All text (not number or date) driven search queries use a contain search__ @@ -178,60 +220,78 @@ __All text (not number or date) driven search queries use a contain search__ | __software__ | -software:"photoshop" | Last edited this app | ### Rest API documentation -Starsky has a Json restful API. There is a Swagger documentation available at `/swagger/index.html` + +Starsky has a Json restful API. There is a Swagger documentation available at `/swagger/index.html` and in the documentation there is a API chapter > Tip: Breaking changes are documented in `./history.md` ### Swagger / OpenAPI + Swagger is an open-source software framework backed by a large ecosystem of tools that helps developers design, build, document, and consume RESTful Web services. There is an swagger definition. You could enable this by setting the following values: -By default this feature is disabled, please use the `AddSwagger` definition in the AppSettings or use the following environment variable: +By default this feature is disabled, please use the `AddSwagger` definition in the AppSettings or +use the following environment variable: ``` app__AddSwagger=true ``` This is the default location of the swagger documentation + ``` http://localhost:4000/swagger ``` ### Known 'There are critical errors in the following components:' -When the UI starts there is an Health API check to make sure that some important components works good + +When the UI starts there is an Health API check to make sure that some important components works +good #### Disk Space errors + - __Storage_StorageFolder__ There is not enough disk space available on the storage folder location -- __Storage_ThumbnailTempFolder__ There is not enough disk space available on the thumbnails folder location +- __Storage_ThumbnailTempFolder__ There is not enough disk space available on the thumbnails folder + location - __Storage_TempFolder__ There is not enough disk space available on the temp folder location #### Folder or file not exist errors + - __Exist_StorageFolder__ The Storage Folder does not exist, please create it first. - __Exist_TempFolder__ The Temp Folder does not exist, please create it first. - __Exist_ExifToolPath__ ExifTool is not linked, you need this to write meta data to files.ExifTool. - Try to remove the _temp folder_ and run the Application again. + Try to remove the _temp folder_ and run the Application again. - __Exist_ThumbnailTempFolder__ The Thumbnail cache Folder does not exist, please create it first. #### Date issues -- __DateAssemblyHealthCheck__ this setting checks if your current datetime is newer than when this application is build + +- __DateAssemblyHealthCheck__ this setting checks if your current datetime is newer than when this + application is build #### ApplicationDbContext, Mysql or Sqlite + There is also a check to make sure the database runs good #### Application Insights -Health issues are also reported to Microsoft Application Insights This only is when a valid key is configured. + +Health issues are also reported to Microsoft Application Insights This only is when a valid key is +configured. ### Known issues #### DiskWatcher in combination with child folders that have no access + When using `useDiskwatcher: true` and there are child folders that are not allowed to read For example the `lost+found` folder + ``` drwx------ 2 root root 16K Apr 16 2018 lost+found ``` + Then DiskWatcher is stopping and retry 20 times before the state will be disabled + ``` [DiskWatcher] (catch-ed) Access to the path '/mnt/external_disk/lost+found' is denied ``` @@ -241,7 +301,8 @@ Solution: make sure that all child folder are accessible #### DiskWatcher in combination with Mac OS APFS Disk When you set `/System/Volumes/Data` to watch for changes this makes the application crash with -`System.ArgumentOutOfRangeException` when a single file is changed. There is currently no solution for this problem other then don't use the Diskwatcher with this location. +`System.ArgumentOutOfRangeException` when a single file is changed. There is currently no solution +for this problem other then don't use the Diskwatcher with this location. ```c# Unhandled exception. System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. diff --git a/starsky/starskycore/ViewModels/EnvFeaturesViewModel.cs b/starsky/starskycore/ViewModels/EnvFeaturesViewModel.cs index b52a03b918..806c84f78a 100644 --- a/starsky/starskycore/ViewModels/EnvFeaturesViewModel.cs +++ b/starsky/starskycore/ViewModels/EnvFeaturesViewModel.cs @@ -2,7 +2,6 @@ namespace starskycore.ViewModels; public class EnvFeaturesViewModel { - /// /// Trash is very dependent on the OS /// @@ -11,5 +10,5 @@ public class EnvFeaturesViewModel /// /// Enable or disable some features on the frontend /// - public bool UseLocalDesktopUi { get; set; } + public bool UseLocalDesktop { get; set; } } diff --git a/starsky/starskytest/Controllers/AppSettingsControllerTest.cs b/starsky/starskytest/Controllers/AppSettingsControllerTest.cs index 6357e57acb..d16a7bc7a4 100644 --- a/starsky/starskytest/Controllers/AppSettingsControllerTest.cs +++ b/starsky/starskytest/Controllers/AppSettingsControllerTest.cs @@ -137,7 +137,7 @@ await controller.UpdateAppSettings( } [TestMethod] - public async Task UpdateAppSettings_UseLocalDesktopUi() + public async Task UpdateAppSettings_UseLocalDesktop() { var appSettings = new AppSettings(); var storage = new FakeIStorage(new List { "/" }); @@ -146,7 +146,7 @@ public async Task UpdateAppSettings_UseLocalDesktopUi() var actionResult = await controller.UpdateAppSettings( - new AppSettingsTransferObject { UseLocalDesktopUi = true }) as JsonResult; + new AppSettingsTransferObject { UseLocalDesktop = true }) as JsonResult; var result = actionResult?.Value as AppSettings; Assert.IsTrue(result?.UseLocalDesktop); } diff --git a/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs b/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs index d63ec28f7e..925ae5b4d4 100644 --- a/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs +++ b/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs @@ -43,7 +43,7 @@ public void FeaturesViewTest_Disabled() Assert.IsNotNull(json); // Assert - Assert.IsFalse(json.UseLocalDesktopUi); + Assert.IsFalse(json.UseLocalDesktop); Assert.IsFalse(json.SystemTrashEnabled); } @@ -61,7 +61,7 @@ public void FeaturesViewTest_Enabled() Assert.IsNotNull(json); // Assert - Assert.IsTrue(json.UseLocalDesktopUi); + Assert.IsTrue(json.UseLocalDesktop); Assert.IsTrue(json.SystemTrashEnabled); } } diff --git a/starsky/starskytest/starsky.feature.settings/Services/UpdateAppSettingsByPathTest.cs b/starsky/starskytest/starsky.feature.settings/Services/UpdateAppSettingsByPathTest.cs index daa6da93dd..10ca323d82 100644 --- a/starsky/starskytest/starsky.feature.settings/Services/UpdateAppSettingsByPathTest.cs +++ b/starsky/starskytest/starsky.feature.settings/Services/UpdateAppSettingsByPathTest.cs @@ -62,7 +62,7 @@ public async Task UpdateAppSettingsAsync_ValidInput_Success_CompareJson() var updateAppSettingsByPath = new UpdateAppSettingsByPath(appSettings, selectorStorage); var appSettingTransferObject = new AppSettingsTransferObject { - StorageFolder = testFolderPath, Verbose = true, UseLocalDesktopUi = null + StorageFolder = testFolderPath, Verbose = true, UseLocalDesktop = null }; // Act @@ -82,7 +82,7 @@ public async Task UpdateAppSettingsAsync_ValidInput_Success_CompareJson() // Assert var expectedResult = "{\n \"app\": {\n \"Verbose\": \"true\",\n \"StorageFolder\": " + // rm quotes - storageFolderJson + ",\n \"UseLocalDesktopUi\": \"false\"\n }\n}"; + storageFolderJson + ",\n \"UseLocalDesktop\": \"false\"\n }\n}"; Assert.AreEqual(expectedResult, result); } diff --git a/starsky/starskytest/starsky.foundation.platform/Helpers/AppSettingsCompareHelperTest.cs b/starsky/starskytest/starsky.foundation.platform/Helpers/AppSettingsCompareHelperTest.cs index 907793d4e1..d9e7f96917 100644 --- a/starsky/starskytest/starsky.foundation.platform/Helpers/AppSettingsCompareHelperTest.cs +++ b/starsky/starskytest/starsky.foundation.platform/Helpers/AppSettingsCompareHelperTest.cs @@ -578,12 +578,7 @@ public void AppSettingsDefaultEditorApplication_Ignore_DefaultOption() AppSettingsCompareHelper.Compare(source, to); - Assert.AreEqual(source.DefaultDesktopEditor.Count, to.DefaultDesktopEditor.Count); - Assert.AreEqual("source/test", source.DefaultDesktopEditor[0].ApplicationPath); - Assert.AreEqual([ - ExtensionRolesHelper.ImageFormat.bmp, - ExtensionRolesHelper.ImageFormat.jpg - ], source.DefaultDesktopEditor[0].ImageFormats); + Assert.AreEqual(0, to.DefaultDesktopEditor.Count); } } } diff --git a/starsky/starskytest/starsky.foundation.platform/JsonConverter/EnumListConverterTest.cs b/starsky/starskytest/starsky.foundation.platform/JsonConverter/EnumListConverterTest.cs new file mode 100644 index 0000000000..69160287fb --- /dev/null +++ b/starsky/starskytest/starsky.foundation.platform/JsonConverter/EnumListConverterTest.cs @@ -0,0 +1,88 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using starsky.foundation.platform.JsonConverter; + +namespace starskytest.starsky.foundation.platform.JsonConverter; + +[TestClass] +public class EnumListConverterTests +{ + [TestMethod] + public void TestYourEnumContainer_Deserialize() + { + // Arrange + const string json = "{\"ValueTypes\":[\"Value1\",\"Value2\",\"Value3\"]}"; + var options = DefaultJsonSerializer.CamelCase; + + // Act + var container = JsonSerializer.Deserialize(json, options); + + // Assert + Assert.IsNotNull(container); + Assert.IsNotNull(container.ValueTypes); + Assert.AreEqual(3, container.ValueTypes.Count); + Assert.AreEqual(ValueType.Value1, container.ValueTypes[0]); + Assert.AreEqual(ValueType.Value2, container.ValueTypes[1]); + Assert.AreEqual(ValueType.Value3, container.ValueTypes[2]); + } + + [TestMethod] + public void TestYourEnumContainer_Serialize() + { + // Arrange + var container = new ValueTypeContainer + { + ValueTypes = [ValueType.Value1, ValueType.Value2] + }; + + // Act + var json = JsonSerializer.Serialize(container, DefaultJsonSerializer.CamelCaseNoEnters); + const string expectedJson = "{\"valueTypes\":[\"Value1\",\"Value2\"]}"; + + // Assert + Assert.AreEqual(expectedJson, json); + } + + [TestMethod] + [ExpectedException(typeof(JsonException), "Unknown enum value: InvalidValue")] + public void TestYourEnumContainer_Deserialize_InvalidValue() + { + // Arrange + const string json = "{\"ValueTypes\":[\"Value1\",\"InvalidValue\",\"Value2\"]}"; + + // Act + JsonSerializer.Deserialize(json, DefaultJsonSerializer.CamelCase); + + // Assert + // Should throw JsonException + } + + [TestMethod] + [ExpectedException(typeof(JsonException), "Unexpected end of JSON input")] + public void TestYourEnumContainer_Deserialize_UnexpectedEnd() + { + // Arrange + const string json = "{\"ValueTypes\":[\"Value1\",\"Value2\""; + + // Act + JsonSerializer.Deserialize(json, DefaultJsonSerializer.CamelCase); + + // Assert + // Should throw JsonException + } +} + +public class ValueTypeContainer +{ + [JsonConverter(typeof(EnumListConverter))] + public List ValueTypes { get; set; } = []; +} + +public enum ValueType +{ + Value1, + Value2, + Value3 +} diff --git a/starsky/starskytest/starsky.foundation.platform/Models/AppSettingsDefaultEditorApplicationTest.cs b/starsky/starskytest/starsky.foundation.platform/Models/AppSettingsDefaultEditorApplicationTest.cs new file mode 100644 index 0000000000..e836bd8351 --- /dev/null +++ b/starsky/starskytest/starsky.foundation.platform/Models/AppSettingsDefaultEditorApplicationTest.cs @@ -0,0 +1,47 @@ +using System.Text.Json; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.platform.Helpers; +using starsky.foundation.platform.JsonConverter; +using starsky.foundation.platform.Models; + +namespace starskytest.starsky.foundation.platform.Models; + +[TestClass] +public class AppSettingsDefaultEditorApplicationTest +{ + [TestMethod] + public void Json_CompareOutputOfString() + { + // Create an instance of MyClass + var myClass = new AppSettingsDefaultEditorApplication + { + ImageFormats = + [ExtensionRolesHelper.ImageFormat.bmp], + ApplicationPath = @"C:\Program Files\MyApp\MyApp.exe" + }; + + // Serialize the object to JSON + var json = JsonSerializer.Serialize(myClass, DefaultJsonSerializer.CamelCaseNoEnters); + + const string expected = "{\"imageFormats\":[\"bmp\"]," + + "\"applicationPath\":\"C:\\\\Program Files\\\\MyApp\\\\MyApp.exe\"}"; + Assert.AreEqual(expected, json); + } + + [TestMethod] + public void Json_CompareInputOfString() + { + // Create an instance of MyClass + const string input = "{\"imageFormats\":[\"bmp\"]," + + "\"applicationPath\":\"C:\\\\Program Files\\\\MyApp\\\\MyApp.exe\"}"; + + // Serialize the object to JSON + var json = JsonSerializer.Deserialize(input, + DefaultJsonSerializer.CamelCaseNoEnters); + + Assert.IsNotNull(json); + Assert.AreEqual(@"C:\Program Files\MyApp\MyApp.exe", json.ApplicationPath); + Assert.AreEqual(1, json.ImageFormats.Count); + Assert.AreEqual(ExtensionRolesHelper.ImageFormat.bmp, json.ImageFormats[0]); + } +} From d1c53d9726891e4533b94b7ba694955c622c8c72 Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 15 Feb 2024 21:29:15 +0100 Subject: [PATCH 032/125] add small delay --- .../OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs index acf2218c1f..e0431c9d15 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/MacOsOpenUrlTests.cs @@ -54,6 +54,9 @@ public async Task TestMethodWithSpecificApp__MacOnly() var isProcess = Process.GetProcessesByName(ConsoleName).ToList() .Exists(p => p.MainModule?.FileName.Contains(ConsoleApp) == true); + + await Task.Delay(3); + for ( var i = 0; i < 15; i++ ) { isProcess = Process.GetProcessesByName(ConsoleName).ToList() From 3615265ff41bc68837757cdee306d22a071eef62 Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 15 Feb 2024 22:39:34 +0100 Subject: [PATCH 033/125] add code // wip --- .../PathImageFormatExistsAppPathModel.cs | 15 +++++ .../Service/OpenEditorDesktopService.cs | 56 ++++++++++++++++--- .../starsky.feature.desktop.csproj | 1 + .../IOpenApplicationNativeService.cs | 1 + .../Interfaces/ISelectorStorage.cs | 3 + .../FakeIOpenApplicationNativeService.cs | 43 ++++++++++++++ .../Service/OpenEditorDesktopServiceTest.cs | 40 +++++++++++++ starsky/starskytest/starskytest.csproj | 1 + 8 files changed, 153 insertions(+), 7 deletions(-) create mode 100644 starsky/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModel.cs create mode 100644 starsky/starskytest/FakeMocks/FakeIOpenApplicationNativeService.cs create mode 100644 starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs diff --git a/starsky/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModel.cs b/starsky/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModel.cs new file mode 100644 index 0000000000..3d3096f024 --- /dev/null +++ b/starsky/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModel.cs @@ -0,0 +1,15 @@ +using starsky.foundation.platform.Helpers; + +namespace starsky.feature.desktop.Models; + +public class PathImageFormatExistsAppPathModel +{ + public string SubPath { get; set; } = string.Empty; + + public ExtensionRolesHelper.ImageFormat ImageFormat { get; set; } = + ExtensionRolesHelper.ImageFormat.notfound; + + public bool Exists { get; set; } = false; + + public string AppPath { get; set; } = string.Empty; +} diff --git a/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs b/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs index bd4a195f28..712ac0a225 100644 --- a/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs +++ b/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs @@ -1,7 +1,12 @@ +using System.ComponentModel.Design; using starsky.feature.desktop.Interfaces; +using starsky.feature.desktop.Models; using starsky.foundation.injection; using starsky.foundation.native.OpenApplicationNative.Interfaces; +using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Models; +using starsky.foundation.storage.Interfaces; +using starsky.foundation.storage.Storage; namespace starsky.feature.desktop.Service; @@ -10,23 +15,60 @@ public class OpenEditorDesktopService : IOpenEditorDesktopService { private readonly AppSettings _appSettings; private readonly IOpenApplicationNativeService _openApplicationNativeService; + private readonly IStorage _iStorage; public OpenEditorDesktopService(AppSettings appSettings, - IOpenApplicationNativeService openApplicationNativeService) + IOpenApplicationNativeService openApplicationNativeService, + ISelectorStorage selectorStorage) { _appSettings = appSettings; _openApplicationNativeService = openApplicationNativeService; + _iStorage = selectorStorage.Get(SelectorStorage.StorageServices.SubPath); } - public void Open(string subPath) + public (bool?, string) Open(List subPaths) { if ( _appSettings.UseLocalDesktop == false ) { - return; + return ( null, "UseLocalDesktop is false" ); } - - // // - // _openApplicationNativeService.Open(subPath); - } + if ( subPaths.Count == 0 ) + { + return ( false, "No files selected" ); + } + + var subPathAndImageFormat = new List(); + foreach ( var subPath in subPaths ) + { + if ( _iStorage.ExistFile(subPath) ) + { + subPathAndImageFormat.Add(new PathImageFormatExistsAppPathModel + { + AppPath = string.Empty, + Exists = false, + ImageFormat = ExtensionRolesHelper.ImageFormat.notfound, + SubPath = subPath + }); + } + + var first50BytesStream = _iStorage.ReadStream(subPath, 50); + var imageFormat = ExtensionRolesHelper.GetImageFormat(first50BytesStream); + first50BytesStream.Dispose(); + + var appSettingsDefaultEditor = + _appSettings.DefaultDesktopEditor.Find(p => p.ImageFormats.Contains(imageFormat)); + + subPathAndImageFormat.Add(new PathImageFormatExistsAppPathModel + { + AppPath = appSettingsDefaultEditor?.ApplicationPath ?? string.Empty, + Exists = true, + ImageFormat = imageFormat, + SubPath = subPath + }); + } + + + return ( false, "TODO: Open Editor" ); + } } diff --git a/starsky/starsky.feature.desktop/starsky.feature.desktop.csproj b/starsky/starsky.feature.desktop/starsky.feature.desktop.csproj index 900d7dc1d3..c7764fb20a 100644 --- a/starsky/starsky.feature.desktop/starsky.feature.desktop.csproj +++ b/starsky/starsky.feature.desktop/starsky.feature.desktop.csproj @@ -14,6 +14,7 @@ + diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs index c795570863..2e962628e4 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs @@ -3,4 +3,5 @@ namespace starsky.foundation.native.OpenApplicationNative.Interfaces; public interface IOpenApplicationNativeService { bool? OpenApplicationAtUrl(List fullPaths, string applicationUrl); + bool? OpenDefault(List fullPaths); } diff --git a/starsky/starsky.foundation.storage/Interfaces/ISelectorStorage.cs b/starsky/starsky.foundation.storage/Interfaces/ISelectorStorage.cs index 1bdcad628f..1ddbeb92fd 100644 --- a/starsky/starsky.foundation.storage/Interfaces/ISelectorStorage.cs +++ b/starsky/starsky.foundation.storage/Interfaces/ISelectorStorage.cs @@ -2,6 +2,9 @@ namespace starsky.foundation.storage.Interfaces { + /// + /// ISelectionStorage + /// public interface ISelectorStorage { IStorage Get(SelectorStorage.StorageServices storageServices); diff --git a/starsky/starskytest/FakeMocks/FakeIOpenApplicationNativeService.cs b/starsky/starskytest/FakeMocks/FakeIOpenApplicationNativeService.cs new file mode 100644 index 0000000000..3dfc44488c --- /dev/null +++ b/starsky/starskytest/FakeMocks/FakeIOpenApplicationNativeService.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Linq; +using starsky.foundation.native.OpenApplicationNative.Interfaces; + +namespace starskytest.FakeMocks; + +public class FakeIOpenApplicationNativeService : IOpenApplicationNativeService +{ + private readonly List _fullFilePaths; + private readonly string _applicationUrl; + + public FakeIOpenApplicationNativeService(List fullPaths, string applicationUrl) + { + _fullFilePaths = fullPaths; + _applicationUrl = applicationUrl; + } + + public string FindPath(List fullPaths) + { + var fullFilePath = string.Empty; + foreach ( var path in fullPaths ) + { + var findPath = _fullFilePaths.Find(p => p == path); + if ( findPath != null ) + { + fullFilePath = findPath; + } + } + + return fullFilePath; + } + + public bool? OpenApplicationAtUrl(List fullPaths, string applicationUrl) + { + var findPath = FindPath(fullPaths); + return !string.IsNullOrEmpty(findPath) && applicationUrl == _applicationUrl; + } + + public bool? OpenDefault(List fullPaths) + { + throw new System.NotImplementedException(); + } +} diff --git a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs new file mode 100644 index 0000000000..a11900313d --- /dev/null +++ b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.feature.desktop.Service; +using starsky.foundation.native.OpenApplicationNative; +using starsky.foundation.platform.Helpers; +using starsky.foundation.platform.Models; +using starskytest.FakeMocks; + +namespace starskytest.starsky.feature.desktop.Service; + +[TestClass] +public class OpenEditorDesktopServiceTest +{ + [TestMethod] + public void TEst() + { + var fakeService = + new FakeIOpenApplicationNativeService(new List { "/test.jpg" }, "test"); + var appSettings = new AppSettings + { + UseLocalDesktop = true, + DefaultDesktopEditor = new List + { + new AppSettingsDefaultEditorApplication + { + ApplicationPath = "app", + ImageFormats = new List + { + ExtensionRolesHelper.ImageFormat.jpg + } + } + } + }; + var service = + new OpenEditorDesktopService(appSettings, fakeService, + new FakeSelectorStorage(new FakeIStorage(new List { "/test.jpg" }))); + + service.Open(new List { "/test.jpg" }); + } +} diff --git a/starsky/starskytest/starskytest.csproj b/starsky/starskytest/starskytest.csproj index db8fe10b4e..8788179aed 100644 --- a/starsky/starskytest/starskytest.csproj +++ b/starsky/starskytest/starskytest.csproj @@ -42,6 +42,7 @@ + From b09c5247ed89c29b24d536bad49dd87f5eb706c0 Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 15 Feb 2024 22:39:29 +0100 Subject: [PATCH 034/125] add mappers --- .../Service/OpenEditorDesktopService.cs | 40 ++- .../IOpenApplicationNativeService.cs | 1 + .../OpenApplicationNativeService.cs | 46 +++ .../CreateFakeStarskyUnixBash.cs | 5 + .../FakeIOpenApplicationNativeService.cs | 20 +- .../OpenApplicationNativeServiceTest.cs | 275 ++++++++++++------ 6 files changed, 293 insertions(+), 94 deletions(-) diff --git a/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs b/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs index 712ac0a225..43800aacb3 100644 --- a/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs +++ b/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs @@ -1,4 +1,3 @@ -using System.ComponentModel.Design; using starsky.feature.desktop.Interfaces; using starsky.feature.desktop.Models; using starsky.foundation.injection; @@ -38,12 +37,24 @@ public OpenEditorDesktopService(AppSettings appSettings, return ( false, "No files selected" ); } - var subPathAndImageFormat = new List(); + var subPathAndImageFormatList = Preflight(subPaths); + + var (openDefaultList, openWithEditorList) = FilterList(subPathAndImageFormatList); + _openApplicationNativeService.OpenDefault(openDefaultList); + _openApplicationNativeService.OpenApplicationAtUrl(openWithEditorList); + + + return ( false, "TODO: Open Editor" ); + } + + private List Preflight(List subPaths) + { + var subPathAndImageFormatList = new List(); foreach ( var subPath in subPaths ) { if ( _iStorage.ExistFile(subPath) ) { - subPathAndImageFormat.Add(new PathImageFormatExistsAppPathModel + subPathAndImageFormatList.Add(new PathImageFormatExistsAppPathModel { AppPath = string.Empty, Exists = false, @@ -59,7 +70,7 @@ public OpenEditorDesktopService(AppSettings appSettings, var appSettingsDefaultEditor = _appSettings.DefaultDesktopEditor.Find(p => p.ImageFormats.Contains(imageFormat)); - subPathAndImageFormat.Add(new PathImageFormatExistsAppPathModel + subPathAndImageFormatList.Add(new PathImageFormatExistsAppPathModel { AppPath = appSettingsDefaultEditor?.ApplicationPath ?? string.Empty, Exists = true, @@ -68,7 +79,26 @@ public OpenEditorDesktopService(AppSettings appSettings, }); } + return subPathAndImageFormatList; + } - return ( false, "TODO: Open Editor" ); + /// + /// Filter the list + /// First is the list with the files that exists and AppPath is set + /// Second is the list with the files that exists but AppPath is not set + /// + /// + /// + private static (List, List<(string SubPath, string AppPath)>) FilterList( + List subPathAndImageFormatList) + { + // TODO: MAP to fullFilePaths + var appPathList = subPathAndImageFormatList + .Where(p => p.Exists && !string.IsNullOrEmpty(p.AppPath)) + .Select(p => p.SubPath).ToList(); + var noAppPathList = subPathAndImageFormatList + .Where(p => p.Exists && string.IsNullOrEmpty(p.AppPath)) + .Select(p => ( p.SubPath, p.AppPath )).ToList(); + return ( appPathList, noAppPathList ); } } diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs index 2e962628e4..d8fdb0ff2e 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs @@ -2,6 +2,7 @@ namespace starsky.foundation.native.OpenApplicationNative.Interfaces; public interface IOpenApplicationNativeService { + bool? OpenApplicationAtUrl(List<(string, string)> fullPathAndApplicationUrl); bool? OpenApplicationAtUrl(List fullPaths, string applicationUrl); bool? OpenDefault(List fullPaths); } diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs b/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs index 856ab90798..b379ab0ba7 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs @@ -8,6 +8,34 @@ namespace starsky.foundation.native.OpenApplicationNative; [Service(typeof(IOpenApplicationNativeService), InjectionLifetime = InjectionLifetime.Scoped)] public class OpenApplicationNativeService : IOpenApplicationNativeService { + /// + /// Open file with specified application + /// + /// List first item is fullFilePath, second is ApplicationUrl + /// true is operation succeed, false failed | null is platform unsupported + public bool? OpenApplicationAtUrl(List<(string, string)> fullPathAndApplicationUrl) + { + if ( fullPathAndApplicationUrl.Count == 0 ) + { + return false; + } + + var filesByApplicationPath = SortToOpenFilesByApplicationPath(fullPathAndApplicationUrl); + + var results = new List(); + foreach ( var (fullFilePaths, applicationPath) in filesByApplicationPath ) + { + results.Add(OpenApplicationAtUrl(fullFilePaths, applicationPath)); + } + + if ( results.Contains(null) ) + { + return null; + } + + return results.TrueForAll(p => p == true); + } + /// /// Open file with specified application /// @@ -26,6 +54,24 @@ public class OpenApplicationNativeService : IOpenApplicationNativeService return macOsOpenResult ?? windowsOpenResult; } + internal static List<(List, string)> SortToOpenFilesByApplicationPath( + List<(string, string)> fullPathAndApplicationUrl) + { + // Group applications by their names + var groupedApplications = fullPathAndApplicationUrl.GroupBy(x => x.Item2).ToList(); + + // Extract full paths for each application and call the implemented function + var results = new List<(List, string)>(); + foreach ( var group in groupedApplications ) + { + var fullPaths = group.Select(item => item.Item1).ToList(); + var applicationUrl = group.Key; + results.Add(( fullPaths, applicationUrl )); + } + + return results; + } + /// /// Open file with default application /// diff --git a/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyUnixBash.cs b/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyUnixBash.cs index 3948b4c550..f839eb4fdf 100644 --- a/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyUnixBash.cs +++ b/starsky/starskytest/FakeCreateAn/CreateFakeStarskyExe/CreateFakeStarskyUnixBash.cs @@ -24,4 +24,9 @@ public CreateFakeStarskyUnixBash() public string StarskyDotStarskyPath { get; set; } = string.Empty; public string FullFilePath { get; set; } = string.Empty; + + /// + /// ApplicationUrl is the same as FullFilePath + /// + public string ApplicationUrl => FullFilePath; } diff --git a/starsky/starskytest/FakeMocks/FakeIOpenApplicationNativeService.cs b/starsky/starskytest/FakeMocks/FakeIOpenApplicationNativeService.cs index 3dfc44488c..df44559387 100644 --- a/starsky/starskytest/FakeMocks/FakeIOpenApplicationNativeService.cs +++ b/starsky/starskytest/FakeMocks/FakeIOpenApplicationNativeService.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using System.Linq; +using starsky.foundation.native.OpenApplicationNative; using starsky.foundation.native.OpenApplicationNative.Interfaces; namespace starskytest.FakeMocks; @@ -30,6 +30,21 @@ public string FindPath(List fullPaths) return fullFilePath; } + public bool? OpenApplicationAtUrl(List<(string, string)> fullPathAndApplicationUrl) + { + var filesByApplicationPath = + OpenApplicationNativeService + .SortToOpenFilesByApplicationPath(fullPathAndApplicationUrl); + + var results = new List(); + foreach ( var (fullFilePaths, applicationPath) in filesByApplicationPath ) + { + results.Add(OpenApplicationAtUrl(fullFilePaths, applicationPath)); + } + + return results.TrueForAll(p => p == true); + } + public bool? OpenApplicationAtUrl(List fullPaths, string applicationUrl) { var findPath = FindPath(fullPaths); @@ -38,6 +53,7 @@ public string FindPath(List fullPaths) public bool? OpenDefault(List fullPaths) { - throw new System.NotImplementedException(); + var findPath = FindPath(fullPaths); + return !string.IsNullOrEmpty(findPath); } } diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs index ee8059497f..11ea436a22 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -9,119 +11,218 @@ using starsky.foundation.platform.Models; using starskytest.FakeCreateAn.CreateFakeStarskyExe; -namespace starskytest.starsky.foundation.native.OpenApplicationNative +namespace starskytest.starsky.foundation.native.OpenApplicationNative; + +[TestClass] +public class OpenApplicationNativeServiceTest { - [TestClass] - public class OpenApplicationNativeServiceTest + private const string Extension = ".starsky"; + private const string ProgramId = "starskytest"; + private const string FileTypeDescription = "Starsky Test File"; + + [TestInitialize] + public void TestInitialize() { - private const string Extension = ".starsky"; - private const string ProgramId = "starskytest"; - private const string FileTypeDescription = "Starsky Test File"; + SetupEnsureAssociationsSet(); + } - [TestInitialize] - public void TestInitialize() + private static CreateFakeStarskyWindowsExe SetupEnsureAssociationsSet() + { + if ( !new AppSettings().IsWindows ) { - SetupEnsureAssociationsSet(); + return new CreateFakeStarskyWindowsExe(); } - private static CreateFakeStarskyWindowsExe SetupEnsureAssociationsSet() - { - if ( !new AppSettings().IsWindows ) + var mock = new CreateFakeStarskyWindowsExe(); + var filePath = mock.FullFilePath; + WindowsSetFileAssociations.EnsureAssociationsSet( + new FileAssociation { - return new CreateFakeStarskyWindowsExe(); - } - - var mock = new CreateFakeStarskyWindowsExe(); - var filePath = mock.FullFilePath; - WindowsSetFileAssociations.EnsureAssociationsSet( - new FileAssociation - { - Extension = Extension, - ProgId = ProgramId, - FileTypeDescription = FileTypeDescription, - ExecutableFilePath = filePath - }); - return mock; - } + Extension = Extension, + ProgId = ProgramId, + FileTypeDescription = FileTypeDescription, + ExecutableFilePath = filePath + }); + return mock; + } - [TestCleanup] - public void TestCleanup() + [TestCleanup] + public void TestCleanup() + { + CleanSetup(); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", + "CA1416:Validate platform compatibility", Justification = "Check does exists")] + private static void CleanSetup() + { + if ( !new AppSettings().IsWindows ) { - CleanSetup(); + return; } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", - "CA1416:Validate platform compatibility", Justification = "Check does exists")] - private static void CleanSetup() - { - if ( !new AppSettings().IsWindows ) - { - return; - } + // Ensure no keys exist before the test starts + Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{Extension}", false); + Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{ProgramId}", false); + } - // Ensure no keys exist before the test starts - Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{Extension}", false); - Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{ProgramId}", false); + [TestMethod] + public async Task Service_OpenDefault_HappyFlow__WindowsOnly() + { + if ( !new AppSettings().IsWindows ) + { + Assert.Inconclusive("This test if for Windows Only"); + return; } - [TestMethod] - public async Task Service_OpenDefault_HappyFlow__WindowsOnly() + var mock = SetupEnsureAssociationsSet(); + + var result = + new OpenApplicationNativeService().OpenDefault([mock.StarskyDotStarskyPath]); + + // retry due due multi threading + if ( result != true ) { - if ( !new AppSettings().IsWindows ) - { - Assert.Inconclusive("This test if for Windows Only"); - return; - } + Console.WriteLine("retry due due multi threading"); + await Task.Delay(100); + SetupEnsureAssociationsSet(); + var service = new OpenApplicationNativeService(); + result = service.OpenDefault([mock.StarskyDotStarskyPath]); + } - var mock = SetupEnsureAssociationsSet(); + Assert.IsTrue(result); + } - var result = - new OpenApplicationNativeService().OpenDefault([mock.StarskyDotStarskyPath]); - // retry due due multi threading - if ( result != true ) - { - Console.WriteLine("retry due due multi threading"); - await Task.Delay(100); - SetupEnsureAssociationsSet(); - var service = new OpenApplicationNativeService(); - result = service.OpenDefault([mock.StarskyDotStarskyPath]); - } - - Assert.IsTrue(result); + [TestMethod] + public void OpenApplicationAtUrl_ZeroItems_SoFalse() + { + var result = new OpenApplicationNativeService().OpenApplicationAtUrl([], "app"); + + // Linux and FreeBSD are not supported + if ( OperatingSystemHelper.GetPlatform() == OSPlatform.Linux || + OperatingSystemHelper.GetPlatform() == OSPlatform.FreeBSD ) + { + Assert.IsNull(result); + return; } + Assert.IsFalse(result); + } + + [TestMethod] + public void OpenDefault_ZeroItemsSo_False() + { + var result = new OpenApplicationNativeService().OpenDefault([]); - [TestMethod] - public void OpenApplicationAtUrl_ZeroItems_SoFalse() + // Linux and FreeBSD are not supported + if ( OperatingSystemHelper.GetPlatform() == OSPlatform.Linux || + OperatingSystemHelper.GetPlatform() == OSPlatform.FreeBSD ) { - var result = new OpenApplicationNativeService().OpenApplicationAtUrl([], "app"); + Assert.IsNull(result); + return; + } - // Linux and FreeBSD are not supported - if ( OperatingSystemHelper.GetPlatform() == OSPlatform.Linux || - OperatingSystemHelper.GetPlatform() == OSPlatform.FreeBSD ) - { - Assert.IsNull(result); - return; - } - - Assert.IsFalse(result); + Assert.IsFalse(result); + } + + [TestMethod] + public void OpenApplicationAtUrl_AllApplicationsSupported_ReturnsTrue() + { + if ( !new AppSettings().IsWindows ) + { + Assert.Inconclusive("This test if for Linux, Mac OS Only"); + return; } - [TestMethod] - public void OpenDefault_ZeroItemsSo_False() + // Arrange + var service = new OpenApplicationNativeService(); + // List is (File Path and Application URL) + + var fullPathAndApplicationUrl = new List<(string, string)> { - var result = new OpenApplicationNativeService().OpenDefault([]); + ( "file1", new CreateFakeStarskyUnixBash().ApplicationUrl ) + }; - // Linux and FreeBSD are not supported - if ( OperatingSystemHelper.GetPlatform() == OSPlatform.Linux || - OperatingSystemHelper.GetPlatform() == OSPlatform.FreeBSD ) - { - Assert.IsNull(result); - return; - } - - Assert.IsFalse(result); - } + // Act + var result = service.OpenApplicationAtUrl(fullPathAndApplicationUrl); + + // Assert + Assert.IsTrue(result); + } + + + [TestMethod] + public void OpenApplicationAtUrl_NoApplications_ReturnsFalse() + { + // Arrange + var service = new OpenApplicationNativeService(); + var fullPathAndApplicationUrl = new List<(string, string)>(); + + // Act + var result = service.OpenApplicationAtUrl(fullPathAndApplicationUrl); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void SortToOpenFilesByApplicationPath_EmptyList_ReturnsEmptyList() + { + // Arrange + var fullPathAndApplicationUrl = new List<(string, string)>(); + + // Act + var result = + OpenApplicationNativeService + .SortToOpenFilesByApplicationPath(fullPathAndApplicationUrl); + + // Assert + Assert.AreEqual(0, result.Count); + } + + [TestMethod] + public void SortToOpenFilesByApplicationPath_SingleApplication_ReturnsSingleGroup() + { + // Arrange + var fullPathAndApplicationUrl = new List<(string, string)> + { + ( "file1", "app1" ), ( "file2", "app1" ), ( "file3", "app1" ) + }; + + // Act + var result = + OpenApplicationNativeService + .SortToOpenFilesByApplicationPath(fullPathAndApplicationUrl); + + // Assert + Assert.AreEqual(1, result.Count); + Assert.AreEqual(3, result[0].Item1.Count); + Assert.AreEqual("app1", result[0].Item2); + } + + [TestMethod] + public void SortToOpenFilesByApplicationPath_MultipleApplications_ReturnsMultipleGroups() + { + // Arrange + var fullPathAndApplicationUrl = new List<(string, string)> + { + ( "file1", "app1" ), + ( "file2", "app2" ), + ( "file3", "app1" ), + ( "file4", "app2" ), + ( "file5", "app3" ) + }; + + // Act + var result = + OpenApplicationNativeService + .SortToOpenFilesByApplicationPath(fullPathAndApplicationUrl); + + // Assert + Assert.AreEqual(3, result.Count); + Assert.IsTrue(result.Any(x => x.Item2 == "app1")); + Assert.IsTrue(result.Any(x => x.Item2 == "app2")); + Assert.IsTrue(result.Any(x => x.Item2 == "app3")); } } From b5674489a1a32ca73914d2ff195172ad987a6eae Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 15 Feb 2024 22:41:03 +0100 Subject: [PATCH 035/125] add extra tests --- .../Helpers/WindowsSetFileAssociations.cs | 12 +++++- .../Helpers/WindowsOpenDesktopAppTests.cs | 31 ++++++++++++++ .../WindowsSetFileAssociationsTests.cs | 3 ++ .../WindowsSetFileAssociationsUnixTests.cs | 40 +++++++++++++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsUnixTests.cs diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs index 090f2e0fe7..4c7ae866e7 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; using Microsoft.Win32; namespace starsky.foundation.native.OpenApplicationNative.Helpers; @@ -15,6 +16,7 @@ public class FileAssociation Justification = "Check build in")] [SuppressMessage("ReSharper", "IdentifierTypo")] [SuppressMessage("ReSharper", "InconsistentNaming")] +[SuppressMessage("Performance", "CA1806:Do not ignore method results")] public static class WindowsSetFileAssociations { /// @@ -35,8 +37,7 @@ public static class WindowsSetFileAssociations private const int SHCNE_ASSOCCHANGED = 0x8000000; private const int SHCNF_FLUSH = 0x1000; - [SuppressMessage("Performance", "CA1806:Do not ignore method results")] - public static void EnsureAssociationsSet(params FileAssociation[] associations) + public static bool EnsureAssociationsSet(params FileAssociation[] associations) { var madeChanges = false; foreach ( var association in associations ) @@ -52,6 +53,8 @@ public static void EnsureAssociationsSet(params FileAssociation[] associations) { SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero); } + + return madeChanges; } internal static bool SetAssociation(string extension, string progId, string fileTypeDescription, @@ -67,6 +70,11 @@ internal static bool SetAssociation(string extension, string progId, string file internal static bool SetKeyDefaultValue(string keyPath, string value) { + if ( !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ) + { + return false; + } + using var key = Registry.CurrentUser.CreateSubKey(keyPath); if ( key.GetValue(null) as string == value ) return false; key.SetValue(null, value); diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs index 2fcc38d2cd..1ab36cc2a8 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs @@ -5,6 +5,7 @@ using starsky.foundation.platform.Models; using starskytest.FakeCreateAn.CreateFakeStarskyExe; using System.Collections.Generic; +using System.ComponentModel; using System.Runtime.InteropServices; using System.Threading.Tasks; using Medallion.Shell; @@ -71,6 +72,22 @@ public void W_OpenDefault_NonWindows() var result = WindowsOpenDesktopApp.OpenDefault(["any value"], OSPlatform.Linux); Assert.IsNull(result); } + + [TestMethod] + public void W_OpenDefault2_NonWindows() + { + if ( new AppSettings().IsWindows ) + { + Assert.Inconclusive("This test if for Unix Only"); + return; + } + + var result = WindowsOpenDesktopApp.OpenDefault(["any value"]); + + Console.WriteLine(result); + + Assert.IsTrue(result); + } [TestMethod] public async Task W_OpenDefault_HappyFlow__WindowsOnly() @@ -104,6 +121,20 @@ public void W_OpenApplicationAtUrl_NonWindows() "app", OSPlatform.Linux); Assert.IsNull(result); } + + [TestMethod] + [ExpectedException(typeof(Win32Exception))] + public void W_OpenApplicationAtUrl2_NonWindows() + { + if ( new AppSettings().IsWindows ) + { + Assert.Inconclusive("This test if for Unix Only"); + return; + } + + WindowsOpenDesktopApp.OpenApplicationAtUrl(new List { "any value" }, + "app"); + } [TestMethod] public void W_OpenApplicationAtUrl_ReturnsTrue_WhenApplicationOpens__WindowsOnly() diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs index f138ea69ac..4d6f625180 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsTests.cs @@ -7,6 +7,9 @@ namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers { + /// + /// Only for Windows - test the WindowsSetFileAssociationsWindows + /// [TestClass] public class WindowsSetFileAssociationsTests { diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsUnixTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsUnixTests.cs new file mode 100644 index 0000000000..1cdd42ec15 --- /dev/null +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsUnixTests.cs @@ -0,0 +1,40 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.native.OpenApplicationNative.Helpers; +using starsky.foundation.platform.Models; + +namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers; + +/// +/// Only for non Windows - so no tests - This feature is windows specific +/// +[TestClass] +public class WindowsSetFileAssociationsUnixTests +{ + [TestMethod] + public void EnsureAssociationsSet() + { + if ( new AppSettings().IsWindows ) + { + Assert.Inconclusive("This test if for Mac and Linux Only"); + return; + } + + var result = WindowsSetFileAssociations.EnsureAssociationsSet(new FileAssociation + { + Extension = ".jpg", + ProgId = "starsky", + FileTypeDescription = "Starsky Test File", + ExecutableFilePath = "/usr/bin/starsky" + }); + + Assert.IsFalse(result); + } + + [TestMethod] + public void SetKeyDefaultValue() + { + // Is false due its unix + var result = WindowsSetFileAssociations.SetKeyDefaultValue("test", "Test"); + Assert.IsFalse(result); + } +} From 5c6337237fabd0c1de6cdd8dd1eae9749befc088 Mon Sep 17 00:00:00 2001 From: Dion Date: Fri, 16 Feb 2024 22:42:23 +0100 Subject: [PATCH 036/125] add check --- .../Helpers/WindowsSetFileAssociationsUnixTests.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsUnixTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsUnixTests.cs index 1cdd42ec15..c0069a536e 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsUnixTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsUnixTests.cs @@ -11,7 +11,7 @@ namespace starskytest.starsky.foundation.native.OpenApplicationNative.Helpers; public class WindowsSetFileAssociationsUnixTests { [TestMethod] - public void EnsureAssociationsSet() + public void EnsureAssociationsSet__UnixOnly() { if ( new AppSettings().IsWindows ) { @@ -31,8 +31,14 @@ public void EnsureAssociationsSet() } [TestMethod] - public void SetKeyDefaultValue() + public void SetKeyDefaultValue__UnixOnly() { + if ( new AppSettings().IsWindows ) + { + Assert.Inconclusive("This test if for Mac and Linux Only"); + return; + } + // Is false due its unix var result = WindowsSetFileAssociations.SetKeyDefaultValue("test", "Test"); Assert.IsFalse(result); From a4243fbc3633769b9c2679c6ab8651de56a49ea8 Mon Sep 17 00:00:00 2001 From: Dion Date: Fri, 16 Feb 2024 16:42:35 +0100 Subject: [PATCH 037/125] change to test on windows --- .../OpenApplicationNativeServiceTest.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs index 11ea436a22..4dd28af9e7 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; @@ -62,8 +63,17 @@ private static void CleanSetup() } // Ensure no keys exist before the test starts - Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{Extension}", false); - Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{ProgramId}", false); + + try + { + Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{Extension}", false); + Registry.CurrentUser.DeleteSubKeyTree($"Software\\Classes\\{ProgramId}", false); + } + catch ( IOException ) + { + // do nothing + } + } [TestMethod] @@ -129,7 +139,7 @@ public void OpenDefault_ZeroItemsSo_False() [TestMethod] public void OpenApplicationAtUrl_AllApplicationsSupported_ReturnsTrue() { - if ( !new AppSettings().IsWindows ) + if ( new AppSettings().IsWindows ) { Assert.Inconclusive("This test if for Linux, Mac OS Only"); return; From c6ecb6f3363296e6843abc73365dde436cd54a51 Mon Sep 17 00:00:00 2001 From: Dion Date: Fri, 16 Feb 2024 16:57:54 +0100 Subject: [PATCH 038/125] change tests --- .../OpenApplicationNativeServiceTest.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs index 4dd28af9e7..90032d00e2 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs @@ -73,7 +73,6 @@ private static void CleanSetup() { // do nothing } - } [TestMethod] @@ -137,11 +136,11 @@ public void OpenDefault_ZeroItemsSo_False() } [TestMethod] - public void OpenApplicationAtUrl_AllApplicationsSupported_ReturnsTrue() + public void OpenApplicationAtUrl_AllApplicationsSupported_ReturnsTrue__LinuxOnly() { - if ( new AppSettings().IsWindows ) + if ( OperatingSystemHelper.GetPlatform() != OSPlatform.Linux ) { - Assert.Inconclusive("This test if for Linux, Mac OS Only"); + Assert.Inconclusive("This test if for Linux Only"); return; } @@ -151,7 +150,8 @@ public void OpenApplicationAtUrl_AllApplicationsSupported_ReturnsTrue() var fullPathAndApplicationUrl = new List<(string, string)> { - ( "file1", new CreateFakeStarskyUnixBash().ApplicationUrl ) + ( new CreateFakeStarskyUnixBash().StarskyDotStarskyPath, + new CreateFakeStarskyUnixBash().ApplicationUrl ) }; // Act From 5f36f2555f7952246cb0e2e68efbb85961fbbbca Mon Sep 17 00:00:00 2001 From: Dion Date: Fri, 16 Feb 2024 17:03:47 +0100 Subject: [PATCH 039/125] fix test --- .../OpenApplicationNativeServiceTest.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs index 90032d00e2..4e9a948723 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -158,7 +157,7 @@ public void OpenApplicationAtUrl_AllApplicationsSupported_ReturnsTrue__LinuxOnly var result = service.OpenApplicationAtUrl(fullPathAndApplicationUrl); // Assert - Assert.IsTrue(result); + Assert.IsNull(result); } @@ -231,8 +230,8 @@ public void SortToOpenFilesByApplicationPath_MultipleApplications_ReturnsMultipl // Assert Assert.AreEqual(3, result.Count); - Assert.IsTrue(result.Any(x => x.Item2 == "app1")); - Assert.IsTrue(result.Any(x => x.Item2 == "app2")); - Assert.IsTrue(result.Any(x => x.Item2 == "app3")); + Assert.IsTrue(result.Exists(x => x.Item2 == "app1")); + Assert.IsTrue(result.Exists(x => x.Item2 == "app2")); + Assert.IsTrue(result.Exists(x => x.Item2 == "app3")); } } From bfb6c99bfbe44ae6766ccf5311a6624b510d7af8 Mon Sep 17 00:00:00 2001 From: Dion Date: Fri, 16 Feb 2024 17:13:34 +0100 Subject: [PATCH 040/125] supress some issues --- .editorconfig | 5 ++++- .../Helpers/WindowsSetFileAssociations.cs | 2 +- .../Trash/Helpers/Example/Clipboard.cs | 15 ++++++++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.editorconfig b/.editorconfig index 5bfd224680..1acd10c0ae 100644 --- a/.editorconfig +++ b/.editorconfig @@ -66,8 +66,11 @@ dotnet_remove_unnecessary_suppression_exclusions = none:warning dotnet_style_namespace_match_folder = true:suggestion -# unused usings +# unused usings (IDE0005) dotnet_diagnostic.IDE0005.severity = warning +# shorter lists +dotnet_diagnostic.IDE0028.severity = silent + # CS4014: Because this call is not awaited, execution of the current method continues before the call is completed. # Consider applying the 'await' operator to the result of the call. diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs index 4c7ae866e7..33e547fdee 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs @@ -28,7 +28,7 @@ public static class WindowsSetFileAssociations /// /// /// - [System.Runtime.InteropServices.DllImport("Shell32.dll")] + [DllImport("Shell32.dll")] [SuppressMessage("Interoperability", "SYSLIB1054:Use \'LibraryImportAttribute\' " + "instead of \'DllImportAttribute\' to generate P/Invoke " + "marshalling code at compile time")] diff --git a/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Example/Clipboard.cs b/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Example/Clipboard.cs index 143b4689b0..7cc37b4656 100644 --- a/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Example/Clipboard.cs +++ b/starsky/starskytest/starsky.foundation.native/Trash/Helpers/Example/Clipboard.cs @@ -1,24 +1,29 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; namespace starskytest.starsky.foundation.native.Trash.Helpers.Example; +[SuppressMessage("Interoperability", "SYSLIB1054:Use \'LibraryImportAttribute\' " + + "instead of \'DllImportAttribute\' to generate P/Invoke " + + "marshalling code at compile time")] +[SuppressMessage("Globalization", "CA2101:Specify marshaling for P/Invoke string arguments")] public static class Clipboard { [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] - static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector); + private static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector); [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] - static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector, string arg1); + private static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector, string arg1); [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] - static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector, IntPtr arg1); + private static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector, IntPtr arg1); [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] - static extern IntPtr sel_registerName(string selectorName); + private static extern IntPtr sel_registerName(string selectorName); [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] - static extern IntPtr objc_getClass(string className); + private static extern IntPtr objc_getClass(string className); public static string? GetText() { From 1949a0b4c2c61872ad1ed4bed8dc15d70367a74d Mon Sep 17 00:00:00 2001 From: Dion Date: Fri, 16 Feb 2024 21:49:09 +0100 Subject: [PATCH 041/125] add tests --- .../Service/OpenEditorDesktopServiceTest.cs | 3 +- .../JsonConverter/EnumListConverterTest.cs | 59 +++++++++++++++---- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs index a11900313d..f960a7b5f4 100644 --- a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs +++ b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.feature.desktop.Service; -using starsky.foundation.native.OpenApplicationNative; using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Models; using starskytest.FakeMocks; @@ -18,7 +17,7 @@ public void TEst() new FakeIOpenApplicationNativeService(new List { "/test.jpg" }, "test"); var appSettings = new AppSettings { - UseLocalDesktop = true, + UseLocalDesktop = true, DefaultDesktopEditor = new List { new AppSettingsDefaultEditorApplication diff --git a/starsky/starskytest/starsky.foundation.platform/JsonConverter/EnumListConverterTest.cs b/starsky/starskytest/starsky.foundation.platform/JsonConverter/EnumListConverterTest.cs index 69160287fb..e79b1392a2 100644 --- a/starsky/starskytest/starsky.foundation.platform/JsonConverter/EnumListConverterTest.cs +++ b/starsky/starskytest/starsky.foundation.platform/JsonConverter/EnumListConverterTest.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Collections.Generic; using System.Text.Json; @@ -9,6 +10,16 @@ namespace starskytest.starsky.foundation.platform.JsonConverter; [TestClass] public class EnumListConverterTests { + [TestMethod] + [ExpectedException(typeof(JsonException))] + public void No_StartArray() + { + // Arrange + const string json = "{\"ValueTypes\":\"Value1\"}"; + var options = DefaultJsonSerializer.CamelCase; + JsonSerializer.Deserialize(json, options); + } + [TestMethod] public void TestYourEnumContainer_Deserialize() { @@ -72,17 +83,43 @@ public void TestYourEnumContainer_Deserialize_UnexpectedEnd() // Assert // Should throw JsonException } -} -public class ValueTypeContainer -{ - [JsonConverter(typeof(EnumListConverter))] - public List ValueTypes { get; set; } = []; -} + [TestMethod] + [ExpectedException(typeof(JsonException))] + public void Read_WhenTokenTypeIsNotStartArray_ThrowsJsonException() + { + // Arrange + var reader = new Utf8JsonReader(Array.Empty()); + var converter = + new EnumListConverter(); // Replace YourEnum with the actual enum type -public enum ValueType -{ - Value1, - Value2, - Value3 + // Act & Assert + converter.Read(ref reader, typeof(List), new JsonSerializerOptions()); + } + + [TestMethod] + [ExpectedException(typeof(JsonException))] + public void Read_WhenTokenTypeIsNotString_ThrowsJsonException() + { + // Arrange + var reader = new Utf8JsonReader(new[] { ( byte )'[', ( byte )'1', ( byte )']' }); + var converter = new EnumListConverter(); + + // Act & Assert + converter.Read(ref reader, typeof(List), new JsonSerializerOptions()); + } + + + public class ValueTypeContainer + { + [JsonConverter(typeof(EnumListConverter))] + public List ValueTypes { get; set; } = []; + } + + public enum ValueType + { + Value1, + Value2, + Value3 + } } From 66453f89008507206ef2aca9ab3a9c3815182733 Mon Sep 17 00:00:00 2001 From: Dion Date: Sun, 18 Feb 2024 22:00:52 +0100 Subject: [PATCH 042/125] add stacktrace (cherry picked from commit e3210c026486353d83d5200b379638c209881636) --- .../Services/PeriodicThumbnailScanHostedService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/starsky/starsky.feature.thumbnail/Services/PeriodicThumbnailScanHostedService.cs b/starsky/starsky.feature.thumbnail/Services/PeriodicThumbnailScanHostedService.cs index 5126dfbf5e..6ec37b0213 100644 --- a/starsky/starsky.feature.thumbnail/Services/PeriodicThumbnailScanHostedService.cs +++ b/starsky/starsky.feature.thumbnail/Services/PeriodicThumbnailScanHostedService.cs @@ -115,7 +115,7 @@ await timer.WaitForNextTickAsync(cancellationToken) ) { _logger.LogError( $"Failed to execute {nameof(PeriodicThumbnailScanHostedService)} " + - $"with exception message {exception.Message}. Good luck next round!", exception); + $"with exception message {exception.Message} {exception.StackTrace}", exception); } return null; From 77cb2d3fbd5a670793e04f676c3521cffd918faf Mon Sep 17 00:00:00 2001 From: Dion Date: Sun, 18 Feb 2024 22:01:40 +0100 Subject: [PATCH 043/125] rm duplicate content --- .editorconfig | 3 --- 1 file changed, 3 deletions(-) diff --git a/.editorconfig b/.editorconfig index 002ba68884..1ca724f52e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -68,9 +68,6 @@ dotnet_style_namespace_match_folder = true:suggestion # unused usings (IDE0005) dotnet_diagnostic.IDE0005.severity = warning -# shorter lists -dotnet_diagnostic.IDE0028.severity = silent - dotnet_diagnostic.IDE0028.severity = silent # IDE0028 = Simplify collection initialization dotnet_diagnostic.IDE0051.severity = silent # Private member is unused From ada1c0508d9295d792bde4dfe135d9d58a4923bb Mon Sep 17 00:00:00 2001 From: Dion Date: Sun, 18 Feb 2024 22:03:08 +0100 Subject: [PATCH 044/125] start working on open-preflight --- .../Interfaces/IOpenEditorDesktopService.cs | 1 + .../Interfaces/IOpenEditorPreflight.cs | 9 ++ .../PathImageFormatExistsAppPathModel.cs | 2 + .../Service/OpenEditorDesktopService.cs | 89 ++++++++++--------- .../Service/OpenEditorPreflight.cs | 88 ++++++++++++++++++ .../starsky.feature.desktop.csproj | 1 + .../Enums/CollectionsOpenType.cs | 11 +++ .../Helpers/AppSettingsCompareHelper.cs | 33 +++++++ .../Models/AppSettings.cs | 9 +- .../OpenEditorDesktopController.cs | 37 ++++++++ starsky/starsky/starsky.csproj | 5 +- .../starskytest/FakeMocks/FakeIImportQuery.cs | 5 +- .../FakeMocks/FakeIOpenEditorPreflight.cs | 15 ++++ .../Service/OpenEditorDesktopServiceTest.cs | 7 +- .../Helpers/AppSettingsCompareHelperTest.cs | 36 ++++++++ 15 files changed, 300 insertions(+), 48 deletions(-) create mode 100644 starsky/starsky.feature.desktop/Interfaces/IOpenEditorPreflight.cs create mode 100644 starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs create mode 100644 starsky/starsky.foundation.platform/Enums/CollectionsOpenType.cs create mode 100644 starsky/starsky/Controllers/OpenEditorDesktopController.cs create mode 100644 starsky/starskytest/FakeMocks/FakeIOpenEditorPreflight.cs diff --git a/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs b/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs index eef6f36ad2..7af9e4e4af 100644 --- a/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs +++ b/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs @@ -2,4 +2,5 @@ namespace starsky.feature.desktop.Interfaces; public interface IOpenEditorDesktopService { + Task<(bool?, string)> OpenAsync(string f, bool collections); } diff --git a/starsky/starsky.feature.desktop/Interfaces/IOpenEditorPreflight.cs b/starsky/starsky.feature.desktop/Interfaces/IOpenEditorPreflight.cs new file mode 100644 index 0000000000..57f4fa37c4 --- /dev/null +++ b/starsky/starsky.feature.desktop/Interfaces/IOpenEditorPreflight.cs @@ -0,0 +1,9 @@ +using starsky.feature.desktop.Models; + +namespace starsky.feature.desktop.Interfaces; + +public interface IOpenEditorPreflight +{ + Task> PreflightAsync( + List inputFilePaths, bool collections); +} diff --git a/starsky/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModel.cs b/starsky/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModel.cs index 3d3096f024..67ab484698 100644 --- a/starsky/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModel.cs +++ b/starsky/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModel.cs @@ -6,6 +6,8 @@ public class PathImageFormatExistsAppPathModel { public string SubPath { get; set; } = string.Empty; + public string FullFilePath { get; set; } = string.Empty; + public ExtensionRolesHelper.ImageFormat ImageFormat { get; set; } = ExtensionRolesHelper.ImageFormat.notfound; diff --git a/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs b/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs index 43800aacb3..6743e00b5f 100644 --- a/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs +++ b/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs @@ -4,8 +4,6 @@ using starsky.foundation.native.OpenApplicationNative.Interfaces; using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Models; -using starsky.foundation.storage.Interfaces; -using starsky.foundation.storage.Storage; namespace starsky.feature.desktop.Service; @@ -14,18 +12,24 @@ public class OpenEditorDesktopService : IOpenEditorDesktopService { private readonly AppSettings _appSettings; private readonly IOpenApplicationNativeService _openApplicationNativeService; - private readonly IStorage _iStorage; + private readonly IOpenEditorPreflight _openEditorPreflight; public OpenEditorDesktopService(AppSettings appSettings, IOpenApplicationNativeService openApplicationNativeService, - ISelectorStorage selectorStorage) + IOpenEditorPreflight openEditorPreflight) { _appSettings = appSettings; _openApplicationNativeService = openApplicationNativeService; - _iStorage = selectorStorage.Get(SelectorStorage.StorageServices.SubPath); + _openEditorPreflight = openEditorPreflight; } - public (bool?, string) Open(List subPaths) + public async Task<(bool?, string)> OpenAsync(string f, bool collections) + { + var inputFilePaths = PathHelper.SplitInputFilePaths(f); + return await OpenAsync(inputFilePaths.ToList(), collections); + } + + private async Task<(bool?, string)> OpenAsync(List subPaths, bool collections) { if ( _appSettings.UseLocalDesktop == false ) { @@ -37,7 +41,8 @@ public OpenEditorDesktopService(AppSettings appSettings, return ( false, "No files selected" ); } - var subPathAndImageFormatList = Preflight(subPaths); + var subPathAndImageFormatList = + await _openEditorPreflight.PreflightAsync(subPaths, collections); var (openDefaultList, openWithEditorList) = FilterList(subPathAndImageFormatList); _openApplicationNativeService.OpenDefault(openDefaultList); @@ -47,40 +52,42 @@ public OpenEditorDesktopService(AppSettings appSettings, return ( false, "TODO: Open Editor" ); } - private List Preflight(List subPaths) - { - var subPathAndImageFormatList = new List(); - foreach ( var subPath in subPaths ) - { - if ( _iStorage.ExistFile(subPath) ) - { - subPathAndImageFormatList.Add(new PathImageFormatExistsAppPathModel - { - AppPath = string.Empty, - Exists = false, - ImageFormat = ExtensionRolesHelper.ImageFormat.notfound, - SubPath = subPath - }); - } - - var first50BytesStream = _iStorage.ReadStream(subPath, 50); - var imageFormat = ExtensionRolesHelper.GetImageFormat(first50BytesStream); - first50BytesStream.Dispose(); - - var appSettingsDefaultEditor = - _appSettings.DefaultDesktopEditor.Find(p => p.ImageFormats.Contains(imageFormat)); - - subPathAndImageFormatList.Add(new PathImageFormatExistsAppPathModel - { - AppPath = appSettingsDefaultEditor?.ApplicationPath ?? string.Empty, - Exists = true, - ImageFormat = imageFormat, - SubPath = subPath - }); - } - - return subPathAndImageFormatList; - } + // [Obsolete("replace with PreflightAsync")] + // private List Preflight(List subPaths) + // { + // var subPathAndImageFormatList = new List(); + // foreach ( var subPath in subPaths ) + // { + // if ( _iStorage.ExistFile(subPath) ) + // { + // subPathAndImageFormatList.Add(new PathImageFormatExistsAppPathModel + // { + // AppPath = string.Empty, + // Exists = false, + // ImageFormat = ExtensionRolesHelper.ImageFormat.notfound, + // SubPath = subPath + // }); + // } + // + // var first50BytesStream = _iStorage.ReadStream(subPath, 50); + // var imageFormat = ExtensionRolesHelper.GetImageFormat(first50BytesStream); + // first50BytesStream.Dispose(); + // + // var appSettingsDefaultEditor = + // _appSettings.DefaultDesktopEditor.Find(p => p.ImageFormats.Contains(imageFormat)); + // + // subPathAndImageFormatList.Add(new PathImageFormatExistsAppPathModel + // { + // AppPath = appSettingsDefaultEditor?.ApplicationPath ?? string.Empty, + // Exists = true, + // ImageFormat = imageFormat, + // SubPath = subPath, + // FullFilePath = _appSettings.DatabasePathToFilePath(subPath) + // }); + // } + // + // return subPathAndImageFormatList; + // } /// /// Filter the list diff --git a/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs b/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs new file mode 100644 index 0000000000..f27c69a04b --- /dev/null +++ b/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs @@ -0,0 +1,88 @@ +using starsky.feature.desktop.Interfaces; +using starsky.feature.desktop.Models; +using starsky.foundation.database.Helpers; +using starsky.foundation.database.Interfaces; +using starsky.foundation.database.Models; +using starsky.foundation.injection; +using starsky.foundation.platform.Models; +using starsky.foundation.storage.Interfaces; +using starsky.foundation.storage.Models; + +namespace starsky.feature.desktop.Service; + +[Service(typeof(IOpenEditorPreflight), InjectionLifetime = InjectionLifetime.Scoped)] +public class OpenEditorPreflight : IOpenEditorPreflight +{ + private readonly IQuery _query; + private readonly AppSettings _appSettings; + private readonly IStorage _iStorage; + + public OpenEditorPreflight(IQuery query, AppSettings appSettings, IStorage iStorage) + { + _query = query; + _appSettings = appSettings; + _iStorage = iStorage; + } + + public async Task> PreflightAsync( + List inputFilePaths, bool collections) + { + var fileIndexItemList = await GetObjectsToOpenFromDatabase(inputFilePaths, collections); + + + var subPathAndImageFormatList = new List(); + + foreach ( var fileIndexItem in fileIndexItemList ) + { + var appSettingsDefaultEditor = + _appSettings.DefaultDesktopEditor.Find(p => + p.ImageFormats.Contains(fileIndexItem.ImageFormat)); + + subPathAndImageFormatList.Add(new PathImageFormatExistsAppPathModel + { + AppPath = appSettingsDefaultEditor?.ApplicationPath ?? string.Empty, + Exists = true, + ImageFormat = fileIndexItem.ImageFormat, + SubPath = fileIndexItem.FilePath!, + FullFilePath = _appSettings.DatabasePathToFilePath(fileIndexItem.FilePath!) + }); + } + + return subPathAndImageFormatList; + } + + private async Task> GetObjectsToOpenFromDatabase( + List inputFilePaths, bool collections) + { + var resultFileIndexItemsList = await _query.GetObjectsByFilePathAsync( + inputFilePaths, collections); + var fileIndexList = new List(); + + foreach ( var fileIndexItem in resultFileIndexItemsList ) + { + // Files that are not on disk + if ( _iStorage.IsFolderOrFile(fileIndexItem.FilePath!) == + FolderOrFileModel.FolderOrFileTypeList.Deleted ) + { + StatusCodesHelper.ReturnExifStatusError(fileIndexItem, + FileIndexItem.ExifStatus.NotFoundSourceMissing, + fileIndexList); + continue; + } + + // Dir is readonly / don't edit + if ( new StatusCodesHelper(_appSettings).IsReadOnlyStatus(fileIndexItem) + == FileIndexItem.ExifStatus.ReadOnly ) + { + StatusCodesHelper.ReturnExifStatusError(fileIndexItem, + FileIndexItem.ExifStatus.ReadOnly, + fileIndexList); + continue; + } + + fileIndexList.Add(fileIndexItem); + } + + return fileIndexList; + } +} diff --git a/starsky/starsky.feature.desktop/starsky.feature.desktop.csproj b/starsky/starsky.feature.desktop/starsky.feature.desktop.csproj index c7764fb20a..fbabcff611 100644 --- a/starsky/starsky.feature.desktop/starsky.feature.desktop.csproj +++ b/starsky/starsky.feature.desktop/starsky.feature.desktop.csproj @@ -11,6 +11,7 @@ + diff --git a/starsky/starsky.foundation.platform/Enums/CollectionsOpenType.cs b/starsky/starsky.foundation.platform/Enums/CollectionsOpenType.cs new file mode 100644 index 0000000000..a6dc6fc939 --- /dev/null +++ b/starsky/starsky.foundation.platform/Enums/CollectionsOpenType.cs @@ -0,0 +1,11 @@ +namespace starsky.foundation.platform.Enums; + +public static class CollectionsOpenType +{ + public enum RawJpegMode + { + Default = 0, + Raw = 1, + Jpeg = 2, + } +} diff --git a/starsky/starsky.foundation.platform/Helpers/AppSettingsCompareHelper.cs b/starsky/starsky.foundation.platform/Helpers/AppSettingsCompareHelper.cs index d41d23a430..60b1395490 100644 --- a/starsky/starsky.foundation.platform/Helpers/AppSettingsCompareHelper.cs +++ b/starsky/starsky.foundation.platform/Helpers/AppSettingsCompareHelper.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.Json; +using starsky.foundation.platform.Enums; using starsky.foundation.platform.Helpers.Compare; using starsky.foundation.platform.JsonConverter; using starsky.foundation.platform.Models; @@ -161,6 +162,18 @@ private static void CompareMultipleSingleItems(PropertyInfo propertyB, CompareDatabaseTypeList(propertyB.Name, sourceIndexItem, oldListStringValue, newListStringValue, differenceList); } + + if ( propertyB.PropertyType == typeof(CollectionsOpenType.RawJpegMode) ) + { + var oldRawJpegModeEnumItem = + ( CollectionsOpenType.RawJpegMode? )propertyInfoFromA.GetValue(sourceIndexItem, + null); + var newRawJpegModeEnumItem = + ( CollectionsOpenType.RawJpegMode? )propertyB.GetValue(updateObject, null); + CompareCollectionsOpenTypeRawJpegMode(propertyB.Name, sourceIndexItem, + oldRawJpegModeEnumItem, + newRawJpegModeEnumItem, differenceList); + } } private static void CompareMultipleListDictionary(PropertyInfo propertyB, @@ -274,6 +287,26 @@ internal static void CompareDatabaseTypeList(string propertyName, differenceList.Add(propertyName.ToLowerInvariant()); } + /// + /// Compare DatabaseTypeList type + /// + /// name of property e.g. DatabaseTypeList + /// source object + /// oldDatabaseTypeList to compare with newDatabaseTypeList + /// newDatabaseTypeList to compare with oldDatabaseTypeList + /// list of different values + private static void CompareCollectionsOpenTypeRawJpegMode(string propertyName, + AppSettings sourceIndexItem, + CollectionsOpenType.RawJpegMode? oldDatabaseTypeList, + CollectionsOpenType.RawJpegMode? newDatabaseTypeList, List differenceList) + { + if ( oldDatabaseTypeList == newDatabaseTypeList || + newDatabaseTypeList == CollectionsOpenType.RawJpegMode.Default ) return; + sourceIndexItem.GetType().GetProperty(propertyName) + ?.SetValue(sourceIndexItem, newDatabaseTypeList, null); + differenceList.Add(propertyName.ToLowerInvariant()); + } + /// /// Compare List String diff --git a/starsky/starsky.foundation.platform/Models/AppSettings.cs b/starsky/starsky.foundation.platform/Models/AppSettings.cs index 1df226e878..ccd187a121 100644 --- a/starsky/starsky.foundation.platform/Models/AppSettings.cs +++ b/starsky/starsky.foundation.platform/Models/AppSettings.cs @@ -8,13 +8,13 @@ using System.Runtime.InteropServices; using System.Text.RegularExpressions; using starsky.foundation.platform.Attributes; +using starsky.foundation.platform.Enums; using starsky.foundation.platform.Helpers; using starsky.foundation.platform.JsonConverter; using TimeZoneConverter; namespace starsky.foundation.platform.Models { - [SuppressMessage("ReSharper", "CA1822")] public sealed class AppSettings { public AppSettings() @@ -771,6 +771,13 @@ public Dictionary? AccountRolesByEmailRegisterOverwrite [PackageTelemetry] public List DefaultDesktopEditor { get; set; } = []; + /// + /// When open with desktop app, open the raw or jpeg (Default: NotSet / Jpeg) + /// + [PackageTelemetry] + public CollectionsOpenType.RawJpegMode DesktopCollectionsOpen { get; set; } = + CollectionsOpenType.RawJpegMode.Default; + /// /// Helps us improve the software /// Please keep this enabled diff --git a/starsky/starsky/Controllers/OpenEditorDesktopController.cs b/starsky/starsky/Controllers/OpenEditorDesktopController.cs new file mode 100644 index 0000000000..60d505e760 --- /dev/null +++ b/starsky/starsky/Controllers/OpenEditorDesktopController.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using starsky.feature.desktop.Interfaces; + +namespace starsky.Controllers; + +public class OpenEditorDesktopController : Controller +{ + private readonly IOpenEditorDesktopService _openEditorDesktopService; + + public OpenEditorDesktopController(IOpenEditorDesktopService openEditorDesktopService) + { + _openEditorDesktopService = openEditorDesktopService; + } + + /// + /// Open a file in the default editor or a specific editor on the desktop + /// + /// single or multiple subPaths + /// to combine files with the same name before the extension + /// + /// returns a list of items from the database + /// subPath not found in the database + /// User unauthorized + [HttpGet("/api/open-editor-desktop/open")] + [Produces("application/json")] +// [ProducesResponseType(typeof(ArchiveViewModel), 200)] + [ProducesResponseType(404)] + [ProducesResponseType(401)] + public async Task OpenAsync( + string f = "/", + bool collections = true) + { + await _openEditorDesktopService.OpenAsync(f, collections); + return Ok(); + } +} diff --git a/starsky/starsky/starsky.csproj b/starsky/starsky/starsky.csproj index 499347d482..2ae3c67693 100644 --- a/starsky/starsky/starsky.csproj +++ b/starsky/starsky/starsky.csproj @@ -58,8 +58,8 @@ 0 - - + + @@ -143,6 +143,7 @@ + diff --git a/starsky/starskytest/FakeMocks/FakeIImportQuery.cs b/starsky/starskytest/FakeMocks/FakeIImportQuery.cs index 0a5d147556..4fdd34c75c 100644 --- a/starsky/starskytest/FakeMocks/FakeIImportQuery.cs +++ b/starsky/starskytest/FakeMocks/FakeIImportQuery.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using starsky.foundation.database.Data; @@ -20,7 +21,7 @@ public FakeIImportQuery(List exist, bool isConnection = true) { _exist = exist; _isConnection = isConnection; - if ( exist == null ) _exist = new List(); + if ( exist == null! ) _exist = new List(); } public FakeIImportQuery() @@ -36,6 +37,8 @@ public FakeIImportQuery() /// /// /// + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + [SuppressMessage("Usage", "IDE0060:Remove unused parameter")] public FakeIImportQuery(IServiceScopeFactory scopeFactory, IConsole console, IWebLogger logger, ApplicationDbContext? dbContext = null) { diff --git a/starsky/starskytest/FakeMocks/FakeIOpenEditorPreflight.cs b/starsky/starskytest/FakeMocks/FakeIOpenEditorPreflight.cs new file mode 100644 index 0000000000..21e98725ca --- /dev/null +++ b/starsky/starskytest/FakeMocks/FakeIOpenEditorPreflight.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using starsky.feature.desktop.Interfaces; +using starsky.feature.desktop.Models; + +namespace starskytest.FakeMocks; + +public class FakeIOpenEditorPreflight : IOpenEditorPreflight +{ + public Task> PreflightAsync(List inputFilePaths, + bool collections) + { + return Task.FromResult(new List()); + } +} diff --git a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs index f960a7b5f4..c365ef2934 100644 --- a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs +++ b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.feature.desktop.Service; using starsky.foundation.platform.Helpers; @@ -11,7 +12,7 @@ namespace starskytest.starsky.feature.desktop.Service; public class OpenEditorDesktopServiceTest { [TestMethod] - public void TEst() + public async Task TEst() { var fakeService = new FakeIOpenApplicationNativeService(new List { "/test.jpg" }, "test"); @@ -32,8 +33,8 @@ public void TEst() }; var service = new OpenEditorDesktopService(appSettings, fakeService, - new FakeSelectorStorage(new FakeIStorage(new List { "/test.jpg" }))); + new FakeIOpenEditorPreflight()); - service.Open(new List { "/test.jpg" }); + await service.OpenAsync(new List { "/test.jpg" }, true); } } diff --git a/starsky/starskytest/starsky.foundation.platform/Helpers/AppSettingsCompareHelperTest.cs b/starsky/starskytest/starsky.foundation.platform/Helpers/AppSettingsCompareHelperTest.cs index d9e7f96917..ba0e7fb708 100644 --- a/starsky/starskytest/starsky.foundation.platform/Helpers/AppSettingsCompareHelperTest.cs +++ b/starsky/starskytest/starsky.foundation.platform/Helpers/AppSettingsCompareHelperTest.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.platform.Enums; using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Models; @@ -91,6 +92,41 @@ public void DatabaseTypeListCompare() Assert.AreEqual(source.DatabaseType, to.DatabaseType); } + [TestMethod] + public void DesktopCollectionsOpenCompare() + { + var source = new AppSettings + { + DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Raw + }; + + var to = new AppSettings + { + DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Jpeg + }; + + AppSettingsCompareHelper.Compare(source, to); + Assert.AreEqual(source.DesktopCollectionsOpen, to.DesktopCollectionsOpen); + } + + [TestMethod] + public void DesktopCollectionsOpenCompare_DefaultIgnore() + { + var source = new AppSettings + { + DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Raw + }; + + var to = new AppSettings + { + DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Default + }; + + AppSettingsCompareHelper.Compare(source, to); + + Assert.AreEqual(CollectionsOpenType.RawJpegMode.Raw, source.DesktopCollectionsOpen); + } + [TestMethod] public void ListAppSettingsPublishProfilesCompare() { From bb1ef0e4add43800434a5e95a920febbcb876e41 Mon Sep 17 00:00:00 2001 From: Dion Date: Sun, 18 Feb 2024 22:46:28 +0100 Subject: [PATCH 045/125] Add mapper && add poc --- .../Interfaces/IOpenEditorDesktopService.cs | 5 +- .../PathImageFormatExistsAppPathModel.cs | 3 +- .../Service/OpenEditorDesktopService.cs | 80 ++++------ .../Service/OpenEditorPreflight.cs | 79 +++++++++- .../Enums/CollectionsOpenType.cs | 4 +- .../AppSettingsFeaturesController.cs | 2 +- .../OpenEditorDesktopController.cs | 2 +- .../default-init-launchSettings.json | 1 + starsky/starsky/appsettings.json | 6 - .../Service/OpenEditorPreflightTests.cs | 137 ++++++++++++++++++ 10 files changed, 246 insertions(+), 73 deletions(-) create mode 100644 starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs diff --git a/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs b/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs index 7af9e4e4af..94590660be 100644 --- a/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs +++ b/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs @@ -1,6 +1,9 @@ +using starsky.feature.desktop.Models; + namespace starsky.feature.desktop.Interfaces; public interface IOpenEditorDesktopService { - Task<(bool?, string)> OpenAsync(string f, bool collections); + Task<(bool?, string, List)> OpenAsync(string f, + bool collections); } diff --git a/starsky/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModel.cs b/starsky/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModel.cs index 67ab484698..389c2a939d 100644 --- a/starsky/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModel.cs +++ b/starsky/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModel.cs @@ -1,3 +1,4 @@ +using starsky.foundation.database.Models; using starsky.foundation.platform.Helpers; namespace starsky.feature.desktop.Models; @@ -11,7 +12,7 @@ public class PathImageFormatExistsAppPathModel public ExtensionRolesHelper.ImageFormat ImageFormat { get; set; } = ExtensionRolesHelper.ImageFormat.notfound; - public bool Exists { get; set; } = false; + public FileIndexItem.ExifStatus Status { get; set; } public string AppPath { get; set; } = string.Empty; } diff --git a/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs b/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs index 6743e00b5f..9af8c1443c 100644 --- a/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs +++ b/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs @@ -1,10 +1,14 @@ +using System.Runtime.CompilerServices; using starsky.feature.desktop.Interfaces; using starsky.feature.desktop.Models; +using starsky.foundation.database.Models; using starsky.foundation.injection; using starsky.foundation.native.OpenApplicationNative.Interfaces; using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Models; +[assembly: InternalsVisibleTo("starskytest")] + namespace starsky.feature.desktop.Service; [Service(typeof(IOpenEditorDesktopService), InjectionLifetime = InjectionLifetime.Scoped)] @@ -23,72 +27,38 @@ public OpenEditorDesktopService(AppSettings appSettings, _openEditorPreflight = openEditorPreflight; } - public async Task<(bool?, string)> OpenAsync(string f, bool collections) + public async Task<(bool?, string, List)> OpenAsync(string f, + bool collections) { var inputFilePaths = PathHelper.SplitInputFilePaths(f); return await OpenAsync(inputFilePaths.ToList(), collections); } - private async Task<(bool?, string)> OpenAsync(List subPaths, bool collections) + internal async Task<(bool?, string, List)> OpenAsync( + List subPaths, bool collections) { if ( _appSettings.UseLocalDesktop == false ) { - return ( null, "UseLocalDesktop is false" ); + return ( null, "UseLocalDesktop feature toggle is disabled", [] ); } - if ( subPaths.Count == 0 ) + var subPathAndImageFormatList = + await _openEditorPreflight.PreflightAsync(subPaths, collections); + + if ( subPathAndImageFormatList.Count == 0 ) { - return ( false, "No files selected" ); + return ( false, "No files selected", [] ); } - var subPathAndImageFormatList = - await _openEditorPreflight.PreflightAsync(subPaths, collections); + var (openDefaultList, openWithEditorList) = + FilterListOpenDefaultEditorAndSpecificEditor(subPathAndImageFormatList); - var (openDefaultList, openWithEditorList) = FilterList(subPathAndImageFormatList); _openApplicationNativeService.OpenDefault(openDefaultList); _openApplicationNativeService.OpenApplicationAtUrl(openWithEditorList); - - return ( false, "TODO: Open Editor" ); + return ( true, "Opened", subPathAndImageFormatList ); } - // [Obsolete("replace with PreflightAsync")] - // private List Preflight(List subPaths) - // { - // var subPathAndImageFormatList = new List(); - // foreach ( var subPath in subPaths ) - // { - // if ( _iStorage.ExistFile(subPath) ) - // { - // subPathAndImageFormatList.Add(new PathImageFormatExistsAppPathModel - // { - // AppPath = string.Empty, - // Exists = false, - // ImageFormat = ExtensionRolesHelper.ImageFormat.notfound, - // SubPath = subPath - // }); - // } - // - // var first50BytesStream = _iStorage.ReadStream(subPath, 50); - // var imageFormat = ExtensionRolesHelper.GetImageFormat(first50BytesStream); - // first50BytesStream.Dispose(); - // - // var appSettingsDefaultEditor = - // _appSettings.DefaultDesktopEditor.Find(p => p.ImageFormats.Contains(imageFormat)); - // - // subPathAndImageFormatList.Add(new PathImageFormatExistsAppPathModel - // { - // AppPath = appSettingsDefaultEditor?.ApplicationPath ?? string.Empty, - // Exists = true, - // ImageFormat = imageFormat, - // SubPath = subPath, - // FullFilePath = _appSettings.DatabasePathToFilePath(subPath) - // }); - // } - // - // return subPathAndImageFormatList; - // } - /// /// Filter the list /// First is the list with the files that exists and AppPath is set @@ -96,16 +66,18 @@ public OpenEditorDesktopService(AppSettings appSettings, /// /// /// - private static (List, List<(string SubPath, string AppPath)>) FilterList( - List subPathAndImageFormatList) + private static (List, List<(string FullFilePath, string AppPath)>) + FilterListOpenDefaultEditorAndSpecificEditor( + IReadOnlyCollection subPathAndImageFormatList) { - // TODO: MAP to fullFilePaths var appPathList = subPathAndImageFormatList - .Where(p => p.Exists && !string.IsNullOrEmpty(p.AppPath)) - .Select(p => p.SubPath).ToList(); + .Where(p => p.Status == FileIndexItem.ExifStatus.Ok && + string.IsNullOrEmpty(p.AppPath)) + .Select(p => p.FullFilePath).ToList(); var noAppPathList = subPathAndImageFormatList - .Where(p => p.Exists && string.IsNullOrEmpty(p.AppPath)) - .Select(p => ( p.SubPath, p.AppPath )).ToList(); + .Where(p => p.Status == FileIndexItem.ExifStatus.Ok && + !string.IsNullOrEmpty(p.AppPath)) + .Select(p => ( p.FullFilePath, p.AppPath )).ToList(); return ( appPathList, noAppPathList ); } } diff --git a/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs b/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs index f27c69a04b..88024569fd 100644 --- a/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs +++ b/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs @@ -4,9 +4,12 @@ using starsky.foundation.database.Interfaces; using starsky.foundation.database.Models; using starsky.foundation.injection; +using starsky.foundation.platform.Enums; +using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Models; using starsky.foundation.storage.Interfaces; using starsky.foundation.storage.Models; +using starsky.foundation.storage.Storage; namespace starsky.feature.desktop.Service; @@ -17,31 +20,31 @@ public class OpenEditorPreflight : IOpenEditorPreflight private readonly AppSettings _appSettings; private readonly IStorage _iStorage; - public OpenEditorPreflight(IQuery query, AppSettings appSettings, IStorage iStorage) + public OpenEditorPreflight(IQuery query, AppSettings appSettings, + ISelectorStorage selectorStorage) { _query = query; _appSettings = appSettings; - _iStorage = iStorage; + _iStorage = selectorStorage.Get(SelectorStorage.StorageServices.SubPath); } public async Task> PreflightAsync( List inputFilePaths, bool collections) { var fileIndexItemList = await GetObjectsToOpenFromDatabase(inputFilePaths, collections); - + fileIndexItemList = GroupByFileCollectionName(fileIndexItemList); var subPathAndImageFormatList = new List(); foreach ( var fileIndexItem in fileIndexItemList ) { - var appSettingsDefaultEditor = - _appSettings.DefaultDesktopEditor.Find(p => - p.ImageFormats.Contains(fileIndexItem.ImageFormat)); + var appSettingsDefaultEditor = _appSettings.DefaultDesktopEditor.Find(p => + p.ImageFormats.Contains(fileIndexItem.ImageFormat)); subPathAndImageFormatList.Add(new PathImageFormatExistsAppPathModel { AppPath = appSettingsDefaultEditor?.ApplicationPath ?? string.Empty, - Exists = true, + Status = fileIndexItem.Status, ImageFormat = fileIndexItem.ImageFormat, SubPath = fileIndexItem.FilePath!, FullFilePath = _appSettings.DatabasePathToFilePath(fileIndexItem.FilePath!) @@ -80,9 +83,71 @@ private async Task> GetObjectsToOpenFromDatabase( continue; } + if ( fileIndexItem.ImageFormat is ExtensionRolesHelper.ImageFormat.xmp + or ExtensionRolesHelper.ImageFormat.meta_json ) + { + continue; + } + + if ( fileIndexItem.Status is FileIndexItem.ExifStatus.Default + or FileIndexItem.ExifStatus.OkAndSame ) + { + fileIndexItem.Status = FileIndexItem.ExifStatus.Ok; + } + fileIndexList.Add(fileIndexItem); } return fileIndexList; } + + internal List GroupByFileCollectionName( + IEnumerable fileIndexInputList) + { + if ( _appSettings.DesktopCollectionsOpen is CollectionsOpenType.RawJpegMode.Default ) + { + _appSettings.DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Jpeg; + } + + var toOpenResultList = new List(); + + var groupedByName = fileIndexInputList.GroupBy(item => item.FileCollectionName); + foreach ( var group in groupedByName ) + { + if ( group.Count() == 1 ) + { + toOpenResultList.AddRange(group); + continue; + } + + var byOrderResultList = new List(); + + switch ( _appSettings.DesktopCollectionsOpen ) + { + case CollectionsOpenType.RawJpegMode.Jpeg: + byOrderResultList.AddRange(group.Where(p => + p.ImageFormat is ExtensionRolesHelper.ImageFormat.jpg + or ExtensionRolesHelper.ImageFormat.bmp + or ExtensionRolesHelper.ImageFormat.png + or ExtensionRolesHelper.ImageFormat.gif + )); + break; + case CollectionsOpenType.RawJpegMode.Raw: + byOrderResultList.AddRange(group.Where(p => + p.ImageFormat == ExtensionRolesHelper.ImageFormat.tiff)); + break; + } + + // When files are not found in the list, take the first one + if ( byOrderResultList.Count == 0 && group.FirstOrDefault() != null ) + { + byOrderResultList.Add(group.First()); + } + + var fileIndexItem = byOrderResultList.OrderBy(p => p.ImageFormat).First(); + toOpenResultList.Add(fileIndexItem); + } + + return toOpenResultList; + } } diff --git a/starsky/starsky.foundation.platform/Enums/CollectionsOpenType.cs b/starsky/starsky.foundation.platform/Enums/CollectionsOpenType.cs index a6dc6fc939..5f54929d1b 100644 --- a/starsky/starsky.foundation.platform/Enums/CollectionsOpenType.cs +++ b/starsky/starsky.foundation.platform/Enums/CollectionsOpenType.cs @@ -5,7 +5,7 @@ public static class CollectionsOpenType public enum RawJpegMode { Default = 0, - Raw = 1, - Jpeg = 2, + Jpeg = 1, + Raw = 2, } } diff --git a/starsky/starsky/Controllers/AppSettingsFeaturesController.cs b/starsky/starsky/Controllers/AppSettingsFeaturesController.cs index da45e3ec18..20c1df28fa 100644 --- a/starsky/starsky/Controllers/AppSettingsFeaturesController.cs +++ b/starsky/starsky/Controllers/AppSettingsFeaturesController.cs @@ -37,7 +37,7 @@ public IActionResult FeaturesView() var shortAppSettings = new EnvFeaturesViewModel { SystemTrashEnabled = _moveToTrashService.IsEnabled(), - UseLocalDesktop = _appSettings.UseLocalDesktop == true + UseLocalDesktop = _appSettings.UseLocalDesktop == true, }; return Json(shortAppSettings); diff --git a/starsky/starsky/Controllers/OpenEditorDesktopController.cs b/starsky/starsky/Controllers/OpenEditorDesktopController.cs index 60d505e760..145b53c835 100644 --- a/starsky/starsky/Controllers/OpenEditorDesktopController.cs +++ b/starsky/starsky/Controllers/OpenEditorDesktopController.cs @@ -28,7 +28,7 @@ public OpenEditorDesktopController(IOpenEditorDesktopService openEditorDesktopSe [ProducesResponseType(404)] [ProducesResponseType(401)] public async Task OpenAsync( - string f = "/", + string f = "", bool collections = true) { await _openEditorDesktopService.OpenAsync(f, collections); diff --git a/starsky/starsky/Properties/default-init-launchSettings.json b/starsky/starsky/Properties/default-init-launchSettings.json index 898032daae..36ab404143 100644 --- a/starsky/starsky/Properties/default-init-launchSettings.json +++ b/starsky/starsky/Properties/default-init-launchSettings.json @@ -25,6 +25,7 @@ "app__EnablePackageTelemetryDebug": "true", "app__DemoUnsafeDeleteStorageFolder": "false", "app__useSystemTrash": "true", + "app__UseLocalDesktop": "true", "app__accountRolesByEmailRegisterOverwrite__demo@qdraw.nl": "Administrator", "app__ThumbnailGenerationIntervalInMinutes": "15", "___app__OpenTelemetry__TracesEndpoint": "http://localhost:4318/v1/traces", diff --git a/starsky/starsky/appsettings.json b/starsky/starsky/appsettings.json index 30b2436933..d9ea300166 100644 --- a/starsky/starsky/appsettings.json +++ b/starsky/starsky/appsettings.json @@ -58,12 +58,6 @@ "DemoUnsafeDeleteStorageFolder": "false", "useSystemTrash": "true", "UseLocalDesktop": "false", - "DefaultDesktopEditor": [ - { - "ApplicationPath": "/Applications/Adobe Photoshop 2024/Adobe Photoshop 2024.app", - "ImageFormats": ["jpg", "bmp", "png", "gif", "tiff"] - } - ], "OpenTelemetry": { "TracesEndpoint": null, "MetricsEndpoint": null, diff --git a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs new file mode 100644 index 0000000000..1b229a4ad7 --- /dev/null +++ b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs @@ -0,0 +1,137 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.feature.desktop.Service; +using starsky.foundation.database.Models; +using starsky.foundation.platform.Enums; +using starsky.foundation.platform.Helpers; +using starsky.foundation.platform.Models; +using starskytest.FakeMocks; + +namespace starskytest.starsky.feature.desktop.Service; + +[TestClass] +public class OpenEditorPreflightTests +{ + [TestMethod] + public void GroupByFileCollectionName_ReturnsCorrectList_WhenAppSettingsIsJpeg() + { + // Arrange + var query = new FakeIQuery(); // You can mock IQuery if needed + var appSettings = + new AppSettings { DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Jpeg }; + var iStorage = new FakeIStorage(); + var preflight = + new OpenEditorPreflight(query, appSettings, new FakeSelectorStorage(iStorage)); + + var fileIndexList = new List + { + new FileIndexItem + { + FileName = "collection1.jpg", + ImageFormat = ExtensionRolesHelper.ImageFormat.jpg + }, + new FileIndexItem + { + FileName = "collection1.tiff", + ImageFormat = ExtensionRolesHelper.ImageFormat.tiff + }, + new FileIndexItem + { + FileName = "collection2.tiff", + ImageFormat = ExtensionRolesHelper.ImageFormat.tiff + }, + new FileIndexItem + { + FileName = "collection3.gif", + ImageFormat = ExtensionRolesHelper.ImageFormat.gif + } + }; + + // Act + var result = preflight.GroupByFileCollectionName(fileIndexList); + + // Assert + Assert.AreEqual(3, result.Count); + + var collection1 = result.Find(p => p.FileCollectionName == "collection1"); + var collection2 = result.Find(p => p.FileCollectionName == "collection2"); + var collection3 = result.Find(p => p.FileCollectionName == "collection3"); + + Assert.AreEqual(ExtensionRolesHelper.ImageFormat.jpg, collection1?.ImageFormat); + Assert.AreEqual(ExtensionRolesHelper.ImageFormat.tiff, collection2?.ImageFormat); + Assert.AreEqual(ExtensionRolesHelper.ImageFormat.gif, collection3?.ImageFormat); + } + + [TestMethod] + public void GroupByFileCollectionName_ReturnsCorrectList_WhenAppSettingsIsRaw() + { + // Arrange + var query = new FakeIQuery(); // You can mock IQuery if needed + var appSettings = + new AppSettings { DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Raw }; + var iStorage = new FakeIStorage(); + var preflight = + new OpenEditorPreflight(query, appSettings, new FakeSelectorStorage(iStorage)); + + var fileIndexList = new List + { + new FileIndexItem + { + FileName = "collection1.jpg", + ImageFormat = ExtensionRolesHelper.ImageFormat.jpg + }, + new FileIndexItem + { + FileName = "collection1.tiff", + ImageFormat = ExtensionRolesHelper.ImageFormat.tiff + } + }; + + // Act + var result = preflight.GroupByFileCollectionName(fileIndexList); + + // Assert + Assert.AreEqual(1, result.Count); + + var collection1 = result.Find(p => p.FileCollectionName == "collection1"); + + Assert.AreEqual(ExtensionRolesHelper.ImageFormat.tiff, collection1?.ImageFormat); + } + + [TestMethod] + public void GroupByFileCollectionName_ReturnsCorrectList_WhenAppSettingsIsOtherType() + { + // Arrange + var query = new FakeIQuery(); // You can mock IQuery if needed + var appSettings = + new AppSettings { DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Raw }; + var iStorage = new FakeIStorage(); + var preflight = + new OpenEditorPreflight(query, appSettings, new FakeSelectorStorage(iStorage)); + + var fileIndexList = new List + { + new FileIndexItem + { + FileName = "collection1.mp4", + ImageFormat = ExtensionRolesHelper.ImageFormat.mp4 + }, + new FileIndexItem + { + FileName = "collection1.jpg", + ImageFormat = ExtensionRolesHelper.ImageFormat.jpg + } + }; + + // Act + var result = preflight.GroupByFileCollectionName(fileIndexList); + + // Assert + Assert.AreEqual(1, result.Count); + + var collection1 = result.Find(p => p.FileCollectionName == "collection1"); + + Assert.AreEqual(ExtensionRolesHelper.ImageFormat.tiff, collection1?.ImageFormat); + } +} From 411fc7e906919d650abf6df16c2c3b2804d29bf9 Mon Sep 17 00:00:00 2001 From: Dion Date: Sun, 18 Feb 2024 22:48:52 +0100 Subject: [PATCH 046/125] update docs --- documentation/.gitignore | 2 ++ .../docs/getting-started/desktop/openwith.md | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 documentation/docs/getting-started/desktop/openwith.md diff --git a/documentation/.gitignore b/documentation/.gitignore index e9d4b99708..4ccec74962 100644 --- a/documentation/.gitignore +++ b/documentation/.gitignore @@ -12,6 +12,8 @@ docs/advanced-options/starsky docs/advanced-options/starskydesktop docs/advanced-options/starsky-tools +!docs/getting-started/desktop + # the output of the build step build diff --git a/documentation/docs/getting-started/desktop/openwith.md b/documentation/docs/getting-started/desktop/openwith.md new file mode 100644 index 0000000000..7b3e3b43a3 --- /dev/null +++ b/documentation/docs/getting-started/desktop/openwith.md @@ -0,0 +1,32 @@ +# Configure Open With + +Settings + + + +## DefaultDesktopEditor + +```json +{ + "DefaultDesktopEditor": [ + { + "ApplicationPath": "/Applications/Adobe Photoshop 2020/Adobe Photoshop 2020.app", + "ImageFormats": ["jpg", "bmp", "png", "gif", "tiff"] + } + ] +} +``` + +### Raw First + +```json +{ + "DesktopCollectionsOpen": "2" +} +``` +### Jpeg first +```json +{ + "DesktopCollectionsOpen": "1" +} +``` \ No newline at end of file From f14b94df131c43aa2ea351c7321e84315a044626 Mon Sep 17 00:00:00 2001 From: Dion Date: Sun, 18 Feb 2024 22:49:10 +0100 Subject: [PATCH 047/125] more clear --- documentation/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/.gitignore b/documentation/.gitignore index 4ccec74962..0ef98f5f98 100644 --- a/documentation/.gitignore +++ b/documentation/.gitignore @@ -12,6 +12,7 @@ docs/advanced-options/starsky docs/advanced-options/starskydesktop docs/advanced-options/starsky-tools +# show desktop folder !docs/getting-started/desktop # the output of the build step From eab1e6791edd2ba2998871ff3b804f22cbba196f Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 19 Feb 2024 17:02:33 +0100 Subject: [PATCH 048/125] fix test --- starsky/starsky/Controllers/OpenEditorDesktopController.cs | 2 ++ .../starsky.feature.desktop/Service/OpenEditorPreflightTests.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/starsky/starsky/Controllers/OpenEditorDesktopController.cs b/starsky/starsky/Controllers/OpenEditorDesktopController.cs index 145b53c835..8cf785dda9 100644 --- a/starsky/starsky/Controllers/OpenEditorDesktopController.cs +++ b/starsky/starsky/Controllers/OpenEditorDesktopController.cs @@ -1,9 +1,11 @@ using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using starsky.feature.desktop.Interfaces; namespace starsky.Controllers; +[Authorize] public class OpenEditorDesktopController : Controller { private readonly IOpenEditorDesktopService _openEditorDesktopService; diff --git a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs index 1b229a4ad7..f096d7c40a 100644 --- a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs +++ b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs @@ -132,6 +132,6 @@ public void GroupByFileCollectionName_ReturnsCorrectList_WhenAppSettingsIsOtherT var collection1 = result.Find(p => p.FileCollectionName == "collection1"); - Assert.AreEqual(ExtensionRolesHelper.ImageFormat.tiff, collection1?.ImageFormat); + Assert.AreEqual(ExtensionRolesHelper.ImageFormat.mp4, collection1?.ImageFormat); } } From 4916376d2a9e7ea72e604be982c4bda3dfe5594c Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 19 Feb 2024 17:54:25 +0100 Subject: [PATCH 049/125] add tests --- .../Helpers/Compare/AreListsEqual.cs | 11 +++ .../Models/AppSettings.cs | 1 + .../Compare/AreListsEqualHelperTests.cs | 82 +++++++++++++++++++ .../JsonConverter/EnumListConverterTest.cs | 34 ++++++++ 4 files changed, 128 insertions(+) create mode 100644 starsky/starskytest/starsky.foundation.platform/Helpers/Compare/AreListsEqualHelperTests.cs diff --git a/starsky/starsky.foundation.platform/Helpers/Compare/AreListsEqual.cs b/starsky/starsky.foundation.platform/Helpers/Compare/AreListsEqual.cs index 54edff6ea8..601a59bb0b 100644 --- a/starsky/starsky.foundation.platform/Helpers/Compare/AreListsEqual.cs +++ b/starsky/starsky.foundation.platform/Helpers/Compare/AreListsEqual.cs @@ -1,11 +1,22 @@ +using System; using System.Collections.Generic; namespace starsky.foundation.platform.Helpers.Compare; public static class AreListsEqualHelper { + /// + /// Compare two lists + /// + /// First list + /// Second list + /// type of both lists + /// true if same, false if not the same internal static bool AreListsEqual(List list1, List list2) { + ArgumentNullException.ThrowIfNull(list1); + ArgumentNullException.ThrowIfNull(list2); + if ( list1.Count != list2.Count ) { return false; diff --git a/starsky/starsky.foundation.platform/Models/AppSettings.cs b/starsky/starsky.foundation.platform/Models/AppSettings.cs index ccd187a121..69fcb6b63a 100644 --- a/starsky/starsky.foundation.platform/Models/AppSettings.cs +++ b/starsky/starsky.foundation.platform/Models/AppSettings.cs @@ -15,6 +15,7 @@ namespace starsky.foundation.platform.Models { + [SuppressMessage("Performance", "CA1822:Mark members as static")] public sealed class AppSettings { public AppSettings() diff --git a/starsky/starskytest/starsky.foundation.platform/Helpers/Compare/AreListsEqualHelperTests.cs b/starsky/starskytest/starsky.foundation.platform/Helpers/Compare/AreListsEqualHelperTests.cs new file mode 100644 index 0000000000..485c1b8ee8 --- /dev/null +++ b/starsky/starskytest/starsky.foundation.platform/Helpers/Compare/AreListsEqualHelperTests.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.platform.Helpers.Compare; + +namespace starskytest.starsky.foundation.platform.Helpers.Compare; + +[TestClass] +public class AreListsEqualHelperTests +{ + [TestMethod] + public void AreListsEqual_SameLists_ReturnsTrue() + { + // Arrange + var list1 = new List { 1, 2, 3 }; + var list2 = new List { 1, 2, 3 }; + + // Act + var result = AreListsEqualHelper.AreListsEqual(list1, list2); + + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public void AreListsEqual_DifferentLists_ReturnsFalse() + { + // Arrange + var list1 = new List { 1, 2, 3 }; + var list2 = new List { 1, 2, 4 }; + + // Act + var result = AreListsEqualHelper.AreListsEqual(list1, list2); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void AreListsEqual_DifferentCountLists_ReturnsFalse() + { + // Arrange + var list1 = new List { 1, 2, 3, 4 }; + var list2 = new List { 1, 4 }; + + // Act + var result = AreListsEqualHelper.AreListsEqual(list1, list2); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void AreListsEqual_NullLists_ArgumentNullException() + { + // Arrange + List list1 = null!; + List list2 = null!; + + // Act + var result = AreListsEqualHelper.AreListsEqual(list1, list2); + + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void AreListsEqual_OneListNull_ArgumentNullException() + { + // Arrange + var list1 = new List { 1, 2, 3 }; + List? list2 = null; + + // Act + var result = AreListsEqualHelper.AreListsEqual(list1, list2!); + + // Assert + Assert.IsFalse(result); + } +} diff --git a/starsky/starskytest/starsky.foundation.platform/JsonConverter/EnumListConverterTest.cs b/starsky/starskytest/starsky.foundation.platform/JsonConverter/EnumListConverterTest.cs index e79b1392a2..635cca1b95 100644 --- a/starsky/starskytest/starsky.foundation.platform/JsonConverter/EnumListConverterTest.cs +++ b/starsky/starskytest/starsky.foundation.platform/JsonConverter/EnumListConverterTest.cs @@ -1,6 +1,7 @@ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Collections.Generic; +using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using starsky.foundation.platform.JsonConverter; @@ -109,6 +110,39 @@ public void Read_WhenTokenTypeIsNotString_ThrowsJsonException() converter.Read(ref reader, typeof(List), new JsonSerializerOptions()); } + [TestMethod] + public void Read_ValidJsonArrayWithEnum() + { + // Arrange + var converter = new EnumListConverter(); + + const string json = "[\"Value1\", \"Value2\"]"; + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json)); + reader.Read(); + + // Act & Assert + var result = + converter.Read(ref reader, typeof(List), new JsonSerializerOptions()); + + CollectionAssert.AreEqual(new List { ValueType.Value1, ValueType.Value2 }, + result); + } + + [TestMethod] + [ExpectedException(typeof(JsonException))] + public void InvalidArrayWithNumber_ThrowJsonException() + { + // Arrange + var converter = new EnumListConverter(); + + // JSON array with a non-string token + const string json = "[1]"; + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json)); + reader.Read(); + + // Act & Assert + converter.Read(ref reader, typeof(List), new JsonSerializerOptions()); + } public class ValueTypeContainer { From 672341923c4f21ffb80dd7bc5f10603563527010 Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 19 Feb 2024 18:18:09 +0100 Subject: [PATCH 050/125] add tests --- .../Service/OpenEditorPreflight.cs | 2 +- .../Service/OpenEditorPreflightTests.cs | 220 +++++++++++++++++- 2 files changed, 220 insertions(+), 2 deletions(-) diff --git a/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs b/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs index 88024569fd..85c26d6e08 100644 --- a/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs +++ b/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs @@ -54,7 +54,7 @@ public async Task> PreflightAsync( return subPathAndImageFormatList; } - private async Task> GetObjectsToOpenFromDatabase( + internal async Task> GetObjectsToOpenFromDatabase( List inputFilePaths, bool collections) { var resultFileIndexItemsList = await _query.GetObjectsByFilePathAsync( diff --git a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs index f096d7c40a..0f5bdf14a5 100644 --- a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs +++ b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs @@ -1,5 +1,6 @@ +using System; using System.Collections.Generic; -using System.Linq; +using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.feature.desktop.Service; using starsky.foundation.database.Models; @@ -13,6 +14,223 @@ namespace starskytest.starsky.feature.desktop.Service; [TestClass] public class OpenEditorPreflightTests { + [TestMethod] + public async Task PreflightAsync_NoAppPath() + { + // Arrange + var queryStub = new FakeIQuery(new List { new FileIndexItem("/test.jpg") }); + var appSettingsStub = new AppSettings(); + var storageStub = new FakeIStorage(new List(), + new List { "/test.jpg" }); + + var inputFilePaths = new List { "/test.jpg" }; + const bool collections = false; + + var openEditorPreflight = new OpenEditorPreflight(queryStub, appSettingsStub, + new FakeSelectorStorage(storageStub)); + + var result = await openEditorPreflight.PreflightAsync(inputFilePaths, collections); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("/test.jpg", result[0].SubPath); + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, result[0].Status); + Assert.AreEqual(ExtensionRolesHelper.ImageFormat.unknown, result[0].ImageFormat); + Assert.IsTrue(result[0].FullFilePath.EndsWith("test.jpg")); + Assert.AreEqual(string.Empty, result[0].AppPath); + } + + [TestMethod] + public async Task PreflightAsync_AppPathSet() + { + // Arrange + var queryStub = new FakeIQuery(new List { new FileIndexItem("/test.jpg") + { + ImageFormat = ExtensionRolesHelper.ImageFormat.jpg + } }); + var appSettingsStub = new AppSettings + { + DefaultDesktopEditor = new List + { + new AppSettingsDefaultEditorApplication + { + ApplicationPath = "/app/test", + ImageFormats = new List + { + ExtensionRolesHelper.ImageFormat.jpg + } + } + } + }; + var storageStub = new FakeIStorage(new List(), + new List { "/test.jpg" }); + + var inputFilePaths = new List { "/test.jpg" }; + const bool collections = false; + + var openEditorPreflight = new OpenEditorPreflight(queryStub, appSettingsStub, + new FakeSelectorStorage(storageStub)); + + var result = await openEditorPreflight.PreflightAsync(inputFilePaths, collections); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("/test.jpg", result[0].SubPath); + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, result[0].Status); + Assert.AreEqual(ExtensionRolesHelper.ImageFormat.jpg, result[0].ImageFormat); + Assert.IsTrue(result[0].FullFilePath.EndsWith("test.jpg")); + Assert.AreEqual("/app/test", result[0].AppPath); + } + + [TestMethod] + public async Task GetObjectsToOpenFromDatabase_NotFound() + { + // Arrange + var queryStub = new FakeIQuery(new List { new FileIndexItem("/test.jpg") }); + var appSettingsStub = new AppSettings(); + var storageStub = new FakeIStorage(); + + // Assuming you have appropriate setup for your test case + var inputFilePaths = new List { "/test.jpg" }; + const bool collections = false; + + var openEditorPreflight = new OpenEditorPreflight(queryStub, appSettingsStub, + new FakeSelectorStorage(storageStub)); + + // Act + var result = + await openEditorPreflight.GetObjectsToOpenFromDatabase(inputFilePaths, collections); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("/test.jpg", result[0].FilePath); + Assert.AreEqual(FileIndexItem.ExifStatus.NotFoundSourceMissing, result[0].Status); + } + + [TestMethod] + public async Task GetObjectsToOpenFromDatabase_ReadOnly() + { + // Arrange + var queryStub = + new FakeIQuery(new List { new FileIndexItem("/readonly/test.jpg") }); + var appSettingsStub = new AppSettings + { + ReadOnlyFolders = new List { "/readonly" } + }; + var storageStub = + new FakeIStorage(new List(), + new List { "/readonly/test.jpg" }); + + // Assuming you have appropriate setup for your test case + var inputFilePaths = new List { "/readonly/test.jpg" }; + const bool collections = false; + + var openEditorPreflight = new OpenEditorPreflight(queryStub, appSettingsStub, + new FakeSelectorStorage(storageStub)); + + // Act + var result = + await openEditorPreflight.GetObjectsToOpenFromDatabase(inputFilePaths, collections); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("/readonly/test.jpg", result[0].FilePath); + Assert.AreEqual(FileIndexItem.ExifStatus.ReadOnly, result[0].Status); + } + + [TestMethod] + public async Task GetObjectsToOpenFromDatabase_SkipXmpSidecar() + { + // Arrange + var queryStub = new FakeIQuery(new List + { + new FileIndexItem("/test.xmp") + { + ImageFormat = ExtensionRolesHelper.ImageFormat.xmp + } + }); + var appSettingsStub = new AppSettings(); + var storageStub = new FakeIStorage(new List(), + new List { "/test.xmp" }); + + // Assuming you have appropriate setup for your test case + var inputFilePaths = new List { "/test.xmp" }; + const bool collections = false; + + var openEditorPreflight = new OpenEditorPreflight(queryStub, appSettingsStub, + new FakeSelectorStorage(storageStub)); + + // Act + var result = + await openEditorPreflight.GetObjectsToOpenFromDatabase(inputFilePaths, collections); + + Assert.AreEqual(0, result.Count); + } + + [TestMethod] + public async Task GetObjectsToOpenFromDatabase_OkStatus() + { + // Arrange + var queryStub = new FakeIQuery(new List + { + new FileIndexItem("/test.mp4") + { + ImageFormat = ExtensionRolesHelper.ImageFormat.mp4 + } + }); + var appSettingsStub = new AppSettings(); + var storageStub = new FakeIStorage(new List(), + new List { "/test.mp4" }); + + // Assuming you have appropriate setup for your test case + var inputFilePaths = new List { "/test.mp4" }; + const bool collections = false; + + var openEditorPreflight = new OpenEditorPreflight(queryStub, appSettingsStub, + new FakeSelectorStorage(storageStub)); + + // Act + var result = + await openEditorPreflight.GetObjectsToOpenFromDatabase(inputFilePaths, collections); + + // Change the status to Ok + Assert.AreEqual(1, result.Count); + Assert.AreEqual("/test.mp4", result[0].FilePath); + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, result[0].Status); + } + + [TestMethod] + public void GroupByFileCollectionName_ReturnsCorrectList_WhenAppSettingsIsDefault() + { + // Arrange + var query = new FakeIQuery(); // You can mock IQuery if needed + var appSettings = + new AppSettings { DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Default }; + var iStorage = new FakeIStorage(); + var preflight = + new OpenEditorPreflight(query, appSettings, new FakeSelectorStorage(iStorage)); + + var fileIndexList = new List + { + new FileIndexItem + { + FileName = "collection1.jpg", + ImageFormat = ExtensionRolesHelper.ImageFormat.jpg + }, + new FileIndexItem + { + FileName = "collection1.tiff", + ImageFormat = ExtensionRolesHelper.ImageFormat.tiff + } + }; + + // Act + var result = preflight.GroupByFileCollectionName(fileIndexList); + + // Assert + Assert.AreEqual(1, result.Count); + + var collection1 = result.Find(p => p.FileCollectionName == "collection1"); + + Assert.AreEqual(ExtensionRolesHelper.ImageFormat.jpg, collection1?.ImageFormat); + } + [TestMethod] public void GroupByFileCollectionName_ReturnsCorrectList_WhenAppSettingsIsJpeg() { From 17876b94be1ce041b47dfadbea9e764d5bac4002 Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 19 Feb 2024 18:49:41 +0100 Subject: [PATCH 051/125] add more tests --- .../PathImageFormatExistsAppPathModel.cs | 2 +- .../Service/OpenEditorPreflight.cs | 31 ++++- .../OpenEditorDesktopController.cs | 15 ++- .../FakeMocks/FakeIOpenEditorPreflight.cs | 9 +- .../PathImageFormatExistsAppPathModelTest.cs | 40 ++++++ .../Service/OpenEditorDesktopServiceTest.cs | 120 +++++++++++++++++- .../Service/OpenEditorPreflightTests.cs | 93 +++++++++++--- 7 files changed, 277 insertions(+), 33 deletions(-) create mode 100644 starsky/starskytest/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModelTest.cs diff --git a/starsky/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModel.cs b/starsky/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModel.cs index 389c2a939d..1000784e0a 100644 --- a/starsky/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModel.cs +++ b/starsky/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModel.cs @@ -12,7 +12,7 @@ public class PathImageFormatExistsAppPathModel public ExtensionRolesHelper.ImageFormat ImageFormat { get; set; } = ExtensionRolesHelper.ImageFormat.notfound; - public FileIndexItem.ExifStatus Status { get; set; } + public FileIndexItem.ExifStatus Status { get; set; } = FileIndexItem.ExifStatus.Default; public string AppPath { get; set; } = string.Empty; } diff --git a/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs b/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs index 85c26d6e08..38195da794 100644 --- a/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs +++ b/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs @@ -6,6 +6,7 @@ using starsky.foundation.injection; using starsky.foundation.platform.Enums; using starsky.foundation.platform.Helpers; +using starsky.foundation.platform.Interfaces; using starsky.foundation.platform.Models; using starsky.foundation.storage.Interfaces; using starsky.foundation.storage.Models; @@ -18,14 +19,18 @@ public class OpenEditorPreflight : IOpenEditorPreflight { private readonly IQuery _query; private readonly AppSettings _appSettings; + private readonly IWebLogger _logger; private readonly IStorage _iStorage; + private readonly IStorage _hostFileSystem; public OpenEditorPreflight(IQuery query, AppSettings appSettings, - ISelectorStorage selectorStorage) + ISelectorStorage selectorStorage, IWebLogger logger) { _query = query; _appSettings = appSettings; + _logger = logger; _iStorage = selectorStorage.Get(SelectorStorage.StorageServices.SubPath); + _hostFileSystem = selectorStorage.Get(SelectorStorage.StorageServices.HostFilesystem); } public async Task> PreflightAsync( @@ -38,12 +43,9 @@ public async Task> PreflightAsync( foreach ( var fileIndexItem in fileIndexItemList ) { - var appSettingsDefaultEditor = _appSettings.DefaultDesktopEditor.Find(p => - p.ImageFormats.Contains(fileIndexItem.ImageFormat)); - subPathAndImageFormatList.Add(new PathImageFormatExistsAppPathModel { - AppPath = appSettingsDefaultEditor?.ApplicationPath ?? string.Empty, + AppPath = GetDesktopEditorPath(fileIndexItem.ImageFormat), Status = fileIndexItem.Status, ImageFormat = fileIndexItem.ImageFormat, SubPath = fileIndexItem.FilePath!, @@ -54,6 +56,25 @@ public async Task> PreflightAsync( return subPathAndImageFormatList; } + private string GetDesktopEditorPath(ExtensionRolesHelper.ImageFormat imageFormat) + { + var appSettingsDefaultEditor = _appSettings.DefaultDesktopEditor.Find(p => + p.ImageFormats.Contains(imageFormat)); + + var appPath = appSettingsDefaultEditor?.ApplicationPath ?? string.Empty; + + // Under Mac OS the ApplicationPath is a .app folder + // Under Windows the ApplicationPath is a .exe file + if ( _hostFileSystem.IsFolderOrFile(appPath) != + FolderOrFileModel.FolderOrFileTypeList.Deleted ) + { + return appPath; + } + + _logger.LogError("[OpenEditorPreflight] AppPath not found: " + appPath); + return string.Empty; + } + internal async Task> GetObjectsToOpenFromDatabase( List inputFilePaths, bool collections) { diff --git a/starsky/starsky/Controllers/OpenEditorDesktopController.cs b/starsky/starsky/Controllers/OpenEditorDesktopController.cs index 8cf785dda9..dbaf77b20f 100644 --- a/starsky/starsky/Controllers/OpenEditorDesktopController.cs +++ b/starsky/starsky/Controllers/OpenEditorDesktopController.cs @@ -33,7 +33,18 @@ public async Task OpenAsync( string f = "", bool collections = true) { - await _openEditorDesktopService.OpenAsync(f, collections); - return Ok(); + var (success, status, list) = + await _openEditorDesktopService.OpenAsync(f, collections); + + switch ( success ) + { + case null: + return BadRequest(status); + case false: + HttpContext.Response.StatusCode = 204; + break; + } + + return Json(list); } } diff --git a/starsky/starskytest/FakeMocks/FakeIOpenEditorPreflight.cs b/starsky/starskytest/FakeMocks/FakeIOpenEditorPreflight.cs index 21e98725ca..f11fa9207f 100644 --- a/starsky/starskytest/FakeMocks/FakeIOpenEditorPreflight.cs +++ b/starsky/starskytest/FakeMocks/FakeIOpenEditorPreflight.cs @@ -7,9 +7,16 @@ namespace starskytest.FakeMocks; public class FakeIOpenEditorPreflight : IOpenEditorPreflight { + private readonly List _content; + + public FakeIOpenEditorPreflight(List content) + { + _content = content; + } + public Task> PreflightAsync(List inputFilePaths, bool collections) { - return Task.FromResult(new List()); + return Task.FromResult(_content); } } diff --git a/starsky/starskytest/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModelTest.cs b/starsky/starskytest/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModelTest.cs new file mode 100644 index 0000000000..a613af43ac --- /dev/null +++ b/starsky/starskytest/starsky.feature.desktop/Models/PathImageFormatExistsAppPathModelTest.cs @@ -0,0 +1,40 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.feature.desktop.Models; +using starsky.foundation.database.Models; +using starsky.foundation.platform.Helpers; + +namespace starskytest.starsky.feature.desktop.Models; + +[TestClass] +public class PathImageFormatExistsAppPathModelTest +{ + [TestMethod] + public void PathImageFormatExistsAppPathModelTest_Default() + { + var model = new PathImageFormatExistsAppPathModel(); + Assert.AreEqual(string.Empty, model.SubPath); + Assert.AreEqual(string.Empty, model.FullFilePath); + Assert.AreEqual(ExtensionRolesHelper.ImageFormat.notfound, model.ImageFormat); + Assert.AreEqual(FileIndexItem.ExifStatus.Default, model.Status); + Assert.AreEqual(string.Empty, model.AppPath); + } + + [TestMethod] + public void PathImageFormatExistsAppPathModelTest_Set() + { + var model = new PathImageFormatExistsAppPathModel + { + AppPath = "test", + Status = FileIndexItem.ExifStatus.Ok, + ImageFormat = ExtensionRolesHelper.ImageFormat.jpg, + SubPath = "/test.jpg", + FullFilePath = "/test.jpg" + }; + + Assert.AreEqual("/test.jpg", model.SubPath); + Assert.AreEqual("/test.jpg", model.FullFilePath); + Assert.AreEqual(ExtensionRolesHelper.ImageFormat.jpg, model.ImageFormat); + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, model.Status); + Assert.AreEqual("test", model.AppPath); + } +} diff --git a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs index c365ef2934..61a24064bd 100644 --- a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs +++ b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.feature.desktop.Models; using starsky.feature.desktop.Service; +using starsky.foundation.database.Models; using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Models; using starskytest.FakeMocks; @@ -12,10 +14,59 @@ namespace starskytest.starsky.feature.desktop.Service; public class OpenEditorDesktopServiceTest { [TestMethod] - public async Task TEst() + public async Task OpenAsync_stringInput_HappyFlow() { var fakeService = new FakeIOpenApplicationNativeService(new List { "/test.jpg" }, "test"); + + var appSettings = new AppSettings + { + UseLocalDesktop = true, + DefaultDesktopEditor = new List + { + new AppSettingsDefaultEditorApplication + { + ApplicationPath = "app", + ImageFormats = new List + { + ExtensionRolesHelper.ImageFormat.jpg + } + } + } + }; + + var preflight = new FakeIOpenEditorPreflight(new List + { + new PathImageFormatExistsAppPathModel + { + AppPath = "test", + Status = FileIndexItem.ExifStatus.Ok, + ImageFormat = ExtensionRolesHelper.ImageFormat.jpg, + SubPath = "/test.jpg", + FullFilePath = "/test.jpg" + } + }); + + var service = + new OpenEditorDesktopService(appSettings, fakeService, preflight); + + var (success, status, list) = + await service.OpenAsync("/test.jpg;/test2.jpg", true); + + Assert.IsTrue(success); + Assert.AreEqual("Opened", status); + Assert.AreEqual(1, list.Count); + Assert.AreEqual("/test.jpg", list[0].SubPath); + Assert.AreEqual("test", list[0].AppPath); + } + + + [TestMethod] + public async Task OpenAsync_ListInput_HappyFlow() + { + var fakeService = + new FakeIOpenApplicationNativeService(new List { "/test.jpg" }, "test"); + var appSettings = new AppSettings { UseLocalDesktop = true, @@ -31,10 +82,71 @@ public async Task TEst() } } }; + + var preflight = new FakeIOpenEditorPreflight(new List + { + new PathImageFormatExistsAppPathModel + { + AppPath = "test", + Status = FileIndexItem.ExifStatus.Ok, + ImageFormat = ExtensionRolesHelper.ImageFormat.jpg, + SubPath = "/test.jpg", + FullFilePath = "/test.jpg" + } + }); + var service = - new OpenEditorDesktopService(appSettings, fakeService, - new FakeIOpenEditorPreflight()); + new OpenEditorDesktopService(appSettings, fakeService, preflight); + + var (success, status, list) = + await service.OpenAsync(new List { "/test.jpg" }, true); + + Assert.IsTrue(success); + Assert.AreEqual("Opened", status); + Assert.AreEqual(1, list.Count); + Assert.AreEqual("/test.jpg", list[0].SubPath); + Assert.AreEqual("test", list[0].AppPath); + } + + [TestMethod] + public async Task OpenAsync_ListInput_NoFilesSelected() + { + var fakeService = + new FakeIOpenApplicationNativeService(new List(), string.Empty); + + var appSettings = new AppSettings { UseLocalDesktop = true }; + + var preflight = new FakeIOpenEditorPreflight(new List()); + + var service = + new OpenEditorDesktopService(appSettings, fakeService, preflight); + + var (success, status, list) = + ( await service.OpenAsync(new List { "/test.jpg" }, true) ); + + Assert.IsFalse(success); + Assert.AreEqual("No files selected", status); + Assert.AreEqual(0, list.Count); + } + + [TestMethod] + public async Task OpenAsync_ListInput_UseLocalDesktop_Null() + { + var fakeService = + new FakeIOpenApplicationNativeService(new List(), string.Empty); + + var appSettings = new AppSettings { UseLocalDesktop = false }; + + var preflight = new FakeIOpenEditorPreflight(new List()); + + var service = + new OpenEditorDesktopService(appSettings, fakeService, preflight); + + var (success, status, list) = + ( await service.OpenAsync(new List { "/test.jpg" }, true) ); - await service.OpenAsync(new List { "/test.jpg" }, true); + Assert.IsNull(success); + Assert.AreEqual("UseLocalDesktop feature toggle is disabled", status); + Assert.AreEqual(0, list.Count); } } diff --git a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs index 0f5bdf14a5..ec51210a6e 100644 --- a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs +++ b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -22,12 +21,12 @@ public async Task PreflightAsync_NoAppPath() var appSettingsStub = new AppSettings(); var storageStub = new FakeIStorage(new List(), new List { "/test.jpg" }); - + var inputFilePaths = new List { "/test.jpg" }; const bool collections = false; - + var openEditorPreflight = new OpenEditorPreflight(queryStub, appSettingsStub, - new FakeSelectorStorage(storageStub)); + new FakeSelectorStorage(storageStub), new FakeIWebLogger()); var result = await openEditorPreflight.PreflightAsync(inputFilePaths, collections); @@ -38,15 +37,18 @@ public async Task PreflightAsync_NoAppPath() Assert.IsTrue(result[0].FullFilePath.EndsWith("test.jpg")); Assert.AreEqual(string.Empty, result[0].AppPath); } - + [TestMethod] - public async Task PreflightAsync_AppPathSet() + public async Task PreflightAsync_AppPathSet_ButNotFound() { // Arrange - var queryStub = new FakeIQuery(new List { new FileIndexItem("/test.jpg") + var queryStub = new FakeIQuery(new List { - ImageFormat = ExtensionRolesHelper.ImageFormat.jpg - } }); + new FileIndexItem("/test.jpg") + { + ImageFormat = ExtensionRolesHelper.ImageFormat.jpg + } + }); var appSettingsStub = new AppSettings { DefaultDesktopEditor = new List @@ -63,12 +65,58 @@ public async Task PreflightAsync_AppPathSet() }; var storageStub = new FakeIStorage(new List(), new List { "/test.jpg" }); - + + var inputFilePaths = new List { "/test.jpg" }; + const bool collections = false; + + var openEditorPreflight = new OpenEditorPreflight(queryStub, appSettingsStub, + new FakeSelectorStorage(storageStub), new FakeIWebLogger()); + + var result = await openEditorPreflight.PreflightAsync(inputFilePaths, collections); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("/test.jpg", result[0].SubPath); + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, result[0].Status); + Assert.AreEqual(ExtensionRolesHelper.ImageFormat.jpg, result[0].ImageFormat); + Assert.IsTrue(result[0].FullFilePath.EndsWith("test.jpg")); + Assert.AreEqual(string.Empty, result[0].AppPath); + } + + [TestMethod] + public async Task PreflightAsync_AppPathSet() + { + // Arrange + var queryStub = new FakeIQuery(new List + { + new FileIndexItem("/test.jpg") + { + ImageFormat = ExtensionRolesHelper.ImageFormat.jpg + } + }); + var appSettingsStub = new AppSettings + { + DefaultDesktopEditor = new List + { + new AppSettingsDefaultEditorApplication + { + ApplicationPath = "/app/test", + ImageFormats = new List + { + ExtensionRolesHelper.ImageFormat.jpg + } + } + } + }; + + // set a folder in the storage for app path location + var storageStub = new FakeIStorage(new List { "/app/test" }, + new List { "/test.jpg" }); + var inputFilePaths = new List { "/test.jpg" }; const bool collections = false; - + var openEditorPreflight = new OpenEditorPreflight(queryStub, appSettingsStub, - new FakeSelectorStorage(storageStub)); + new FakeSelectorStorage(storageStub), new FakeIWebLogger()); var result = await openEditorPreflight.PreflightAsync(inputFilePaths, collections); @@ -80,6 +128,7 @@ public async Task PreflightAsync_AppPathSet() Assert.AreEqual("/app/test", result[0].AppPath); } + [TestMethod] public async Task GetObjectsToOpenFromDatabase_NotFound() { @@ -93,7 +142,7 @@ public async Task GetObjectsToOpenFromDatabase_NotFound() const bool collections = false; var openEditorPreflight = new OpenEditorPreflight(queryStub, appSettingsStub, - new FakeSelectorStorage(storageStub)); + new FakeSelectorStorage(storageStub), new FakeIWebLogger()); // Act var result = @@ -123,7 +172,7 @@ public async Task GetObjectsToOpenFromDatabase_ReadOnly() const bool collections = false; var openEditorPreflight = new OpenEditorPreflight(queryStub, appSettingsStub, - new FakeSelectorStorage(storageStub)); + new FakeSelectorStorage(storageStub), new FakeIWebLogger()); // Act var result = @@ -154,7 +203,7 @@ public async Task GetObjectsToOpenFromDatabase_SkipXmpSidecar() const bool collections = false; var openEditorPreflight = new OpenEditorPreflight(queryStub, appSettingsStub, - new FakeSelectorStorage(storageStub)); + new FakeSelectorStorage(storageStub), new FakeIWebLogger()); // Act var result = @@ -183,7 +232,7 @@ public async Task GetObjectsToOpenFromDatabase_OkStatus() const bool collections = false; var openEditorPreflight = new OpenEditorPreflight(queryStub, appSettingsStub, - new FakeSelectorStorage(storageStub)); + new FakeSelectorStorage(storageStub), new FakeIWebLogger()); // Act var result = @@ -204,7 +253,8 @@ public void GroupByFileCollectionName_ReturnsCorrectList_WhenAppSettingsIsDefaul new AppSettings { DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Default }; var iStorage = new FakeIStorage(); var preflight = - new OpenEditorPreflight(query, appSettings, new FakeSelectorStorage(iStorage)); + new OpenEditorPreflight(query, appSettings, new FakeSelectorStorage(iStorage), + new FakeIWebLogger()); var fileIndexList = new List { @@ -240,7 +290,8 @@ public void GroupByFileCollectionName_ReturnsCorrectList_WhenAppSettingsIsJpeg() new AppSettings { DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Jpeg }; var iStorage = new FakeIStorage(); var preflight = - new OpenEditorPreflight(query, appSettings, new FakeSelectorStorage(iStorage)); + new OpenEditorPreflight(query, appSettings, new FakeSelectorStorage(iStorage), + new FakeIWebLogger()); var fileIndexList = new List { @@ -290,7 +341,8 @@ public void GroupByFileCollectionName_ReturnsCorrectList_WhenAppSettingsIsRaw() new AppSettings { DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Raw }; var iStorage = new FakeIStorage(); var preflight = - new OpenEditorPreflight(query, appSettings, new FakeSelectorStorage(iStorage)); + new OpenEditorPreflight(query, appSettings, new FakeSelectorStorage(iStorage), + new FakeIWebLogger()); var fileIndexList = new List { @@ -326,7 +378,8 @@ public void GroupByFileCollectionName_ReturnsCorrectList_WhenAppSettingsIsOtherT new AppSettings { DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Raw }; var iStorage = new FakeIStorage(); var preflight = - new OpenEditorPreflight(query, appSettings, new FakeSelectorStorage(iStorage)); + new OpenEditorPreflight(query, appSettings, new FakeSelectorStorage(iStorage), + new FakeIWebLogger()); var fileIndexList = new List { From 2e2f2ef097752a7b540c75ad818a2cb357667d11 Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 19 Feb 2024 18:52:17 +0100 Subject: [PATCH 052/125] rename --- ...ktopController.cs => DesktopEditorController.cs} | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) rename starsky/starsky/Controllers/{OpenEditorDesktopController.cs => DesktopEditorController.cs} (73%) diff --git a/starsky/starsky/Controllers/OpenEditorDesktopController.cs b/starsky/starsky/Controllers/DesktopEditorController.cs similarity index 73% rename from starsky/starsky/Controllers/OpenEditorDesktopController.cs rename to starsky/starsky/Controllers/DesktopEditorController.cs index dbaf77b20f..846eb2e262 100644 --- a/starsky/starsky/Controllers/OpenEditorDesktopController.cs +++ b/starsky/starsky/Controllers/DesktopEditorController.cs @@ -1,16 +1,18 @@ +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using starsky.feature.desktop.Interfaces; +using starsky.feature.desktop.Models; namespace starsky.Controllers; [Authorize] -public class OpenEditorDesktopController : Controller +public class DesktopEditorController : Controller { private readonly IOpenEditorDesktopService _openEditorDesktopService; - public OpenEditorDesktopController(IOpenEditorDesktopService openEditorDesktopService) + public DesktopEditorController(IOpenEditorDesktopService openEditorDesktopService) { _openEditorDesktopService = openEditorDesktopService; } @@ -24,10 +26,11 @@ public OpenEditorDesktopController(IOpenEditorDesktopService openEditorDesktopSe /// returns a list of items from the database /// subPath not found in the database /// User unauthorized - [HttpGet("/api/open-editor-desktop/open")] + [HttpGet("/api/desktop-editor/open")] [Produces("application/json")] -// [ProducesResponseType(typeof(ArchiveViewModel), 200)] - [ProducesResponseType(404)] + [ProducesResponseType(typeof(List), 200)] + [ProducesResponseType(typeof(List), 204)] + [ProducesResponseType(typeof(string), 400)] [ProducesResponseType(401)] public async Task OpenAsync( string f = "", From 295175ef753baf975fbcf27874e4825d04841a26 Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 19 Feb 2024 19:00:09 +0100 Subject: [PATCH 053/125] add test --- .../DesktopEditorControllerTest.cs | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 starsky/starskytest/Controllers/DesktopEditorControllerTest.cs diff --git a/starsky/starskytest/Controllers/DesktopEditorControllerTest.cs b/starsky/starskytest/Controllers/DesktopEditorControllerTest.cs new file mode 100644 index 0000000000..c8c90938ca --- /dev/null +++ b/starsky/starskytest/Controllers/DesktopEditorControllerTest.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.Controllers; +using starsky.feature.desktop.Models; +using starsky.feature.desktop.Service; +using starsky.foundation.database.Models; +using starsky.foundation.platform.Helpers; +using starsky.foundation.platform.Models; +using starskytest.FakeMocks; + +namespace starskytest.Controllers; + +[TestClass] +public class DesktopEditorControllerTest +{ + [TestMethod] + public async Task OpenAsync_FeatureToggleDisabled() + { + var controller = new DesktopEditorController(new OpenEditorDesktopService(new AppSettings(), + new FakeIOpenApplicationNativeService(new List(), "test"), + new FakeIOpenEditorPreflight(new List()))); + + controller.ControllerContext = new ControllerContext + { + HttpContext = new DefaultHttpContext() + }; + + var result = await controller.OpenAsync("/test.jpg;/test2.jpg"); + var castedResult = ( BadRequestObjectResult )result; + + Assert.AreEqual(400, castedResult.StatusCode); + } + + [TestMethod] + public async Task OpenAsync_NoResultsBack() + { + var controller = new DesktopEditorController(new OpenEditorDesktopService( + new AppSettings { UseLocalDesktop = true }, + new FakeIOpenApplicationNativeService(new List(), "test"), + new FakeIOpenEditorPreflight(new List()))); + + controller.ControllerContext = new ControllerContext + { + HttpContext = new DefaultHttpContext() + }; + + var result = await controller.OpenAsync("/test.jpg;/test2.jpg"); + Assert.AreEqual(204, controller.HttpContext.Response.StatusCode); + + var castedResult = ( JsonResult )result; + var arrayValues = ( List? )castedResult.Value; + + Assert.AreEqual(0, arrayValues?.Count); + } + + [TestMethod] + public async Task OpenAsync_HappyFlow() + { + var preflight = new FakeIOpenEditorPreflight(new List + { + new PathImageFormatExistsAppPathModel + { + AppPath = "test", + Status = FileIndexItem.ExifStatus.Ok, + ImageFormat = ExtensionRolesHelper.ImageFormat.jpg, + SubPath = "/test.jpg", + FullFilePath = "/test.jpg" + } + }); + var controller = new DesktopEditorController(new OpenEditorDesktopService( + new AppSettings { UseLocalDesktop = true }, + new FakeIOpenApplicationNativeService(new List(), "test"), + preflight)); + + controller.ControllerContext = new ControllerContext + { + HttpContext = new DefaultHttpContext() + }; + + var result = await controller.OpenAsync("/test.jpg;/test2.jpg"); + Assert.AreEqual(200, controller.HttpContext.Response.StatusCode); + + var castedResult = ( JsonResult )result; + var arrayValues = ( List? )castedResult.Value; + + Assert.AreEqual(1, arrayValues?.Count); + } +} From 60e61c5e855f816b1c2718ed00319d5a80819560 Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 19 Feb 2024 20:18:55 +0100 Subject: [PATCH 054/125] retry --- .../Import/ImportQueryTest.cs | 2 +- .../QueryTest/QueryRemoveItemAsyncTest.cs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/starsky/starskytest/starsky.foundation.database/Import/ImportQueryTest.cs b/starsky/starskytest/starsky.foundation.database/Import/ImportQueryTest.cs index 2ccac67318..d2c1d5f4da 100644 --- a/starsky/starskytest/starsky.foundation.database/Import/ImportQueryTest.cs +++ b/starsky/starskytest/starsky.foundation.database/Import/ImportQueryTest.cs @@ -220,7 +220,7 @@ public async Task RemoveItemAsync_RemovesItemFromImportIndex() } [TestMethod] - public async Task RemoveAsync_Disposed() + public async Task ImportQuery_RemoveAsync_Disposed() { var addedItems = new List { diff --git a/starsky/starskytest/starsky.foundation.database/QueryTest/QueryRemoveItemAsyncTest.cs b/starsky/starskytest/starsky.foundation.database/QueryTest/QueryRemoveItemAsyncTest.cs index 43b86ac328..08a5b47477 100644 --- a/starsky/starskytest/starsky.foundation.database/QueryTest/QueryRemoveItemAsyncTest.cs +++ b/starsky/starskytest/starsky.foundation.database/QueryTest/QueryRemoveItemAsyncTest.cs @@ -64,7 +64,7 @@ public async Task QueryRemoveItemAsyncTest_List_AddOneItem() } [TestMethod] - public async Task RemoveAsync_Disposed() + public async Task Query_RemoveAsync_Disposed() { var addedItems = new List { @@ -81,11 +81,11 @@ public async Task RemoveAsync_Disposed() // Dispose here await dbContextDisposed.DisposeAsync(); - - await new Query(dbContextDisposed, - new AppSettings { - AddMemoryCache = false - }, serviceScopeFactory, new FakeIWebLogger(), new FakeMemoryCache()).RemoveItemAsync(addedItems); + + var service = new Query(dbContextDisposed, + new AppSettings { AddMemoryCache = false }, serviceScopeFactory, new FakeIWebLogger(), + new FakeMemoryCache()); + await service.RemoveItemAsync(addedItems); var context = new InjectServiceScope(serviceScopeFactory).Context(); var queryFromDb = context.FileIndex.Where(p => From 3f28f3fdbee7118d735f03619b4e28eb242a610e Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 19 Feb 2024 20:22:21 +0100 Subject: [PATCH 055/125] rename feature --- documentation/static/openapi/openapi.json | 4 ++-- history.md | 2 +- starsky-tools/mock/api/env/features.json | 2 +- .../internal/inline-search-suggest.spec.tsx | 4 ++-- .../menu-inline-search/internal/inline-search-suggest.tsx | 4 ++-- .../molecules/menu-inline-search/menu-inline-search.spec.tsx | 4 ++-- starsky/starsky/clientapp/src/interfaces/IEnvFeatures.ts | 2 +- starskydesktop/src/app/child-process/setup-child-process.ts | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/documentation/static/openapi/openapi.json b/documentation/static/openapi/openapi.json index 39a4ca64d6..ca2178b38d 100644 --- a/documentation/static/openapi/openapi.json +++ b/documentation/static/openapi/openapi.json @@ -1967,7 +1967,7 @@ }, { "unresolvedReference": false, - "name": "UseLocalDesktopUi", + "name": "UseLocalDesktop", "in": 0, "required": false, "deprecated": false, @@ -12056,7 +12056,7 @@ "extensions": {}, "unresolvedReference": false }, - "useLocalDesktopUi": { + "useLocalDesktop": { "type": "boolean", "readOnly": false, "writeOnly": false, diff --git a/history.md b/history.md index 8a7fdd9d12..e13f965622 100644 --- a/history.md +++ b/history.md @@ -194,7 +194,7 @@ _Known issues #1106, #1107 and #1108_ - [x] (Added) _Front-end_ Add MoreMenu remove current folder (PR #1085) - [x] (Changed) _Front-end_ MoreMenu refactor (PR #1085) - [x] (Changed) _Front-end_ Removal of Directories (PR #1085) -- [x] (Changed) _Front-end_ Hide parts of menu in UseLocalDesktopUi mode (PR #1087) +- [x] (Changed) _Front-end_ Hide parts of menu in UseLocalDesktop(Ui) mode (PR #1087) - [x] (Fixed) _Front-end_ Fixed 300 eslint issues (PR #1087) - [x] (Changed) _Back-end_ when deleting in systemTrash mode xmp files are now deleted (PR #1088) - [x] (Changed) _Back-end_ test when deleting in server mode: xmp files are gone fixed (PR #1088) diff --git a/starsky-tools/mock/api/env/features.json b/starsky-tools/mock/api/env/features.json index 3b514380a0..5246a3e88b 100644 --- a/starsky-tools/mock/api/env/features.json +++ b/starsky-tools/mock/api/env/features.json @@ -1 +1 @@ -{"systemTrashEnabled":false,"useLocalDesktopUi":false} \ No newline at end of file +{"systemTrashEnabled":false,"useLocalDesktop":false} \ No newline at end of file diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-inline-search/internal/inline-search-suggest.spec.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-inline-search/internal/inline-search-suggest.spec.tsx index 305d7b0723..3d42704dcd 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-inline-search/internal/inline-search-suggest.spec.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-inline-search/internal/inline-search-suggest.spec.tsx @@ -69,7 +69,7 @@ describe("inline-search-suggest", () => { expect(queryByText("Trash")).toBeNull(); }); - it("hides logout menu item when useLocalDesktopUi is enabled", () => { + it("hides logout menu item when useLocalDesktop is enabled", () => { const props2 = { suggest: [], setFormFocus: jest.fn(), @@ -79,7 +79,7 @@ describe("inline-search-suggest", () => { } as any }, featuresResult: { - data: { useLocalDesktopUi: true }, + data: { useLocalDesktop: true }, statusCode: 200 } as IConnectionDefault, defaultText: "default text", diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-inline-search/internal/inline-search-suggest.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-inline-search/internal/inline-search-suggest.tsx index eecc32f94e..830d2b549b 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-inline-search/internal/inline-search-suggest.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-inline-search/internal/inline-search-suggest.tsx @@ -24,12 +24,12 @@ const InlineSearchSuggest: React.FunctionComponent = useEffect(() => { const dataFeatures = props.featuresResult?.data as IEnvFeatures | undefined; - if (dataFeatures?.systemTrashEnabled || dataFeatures?.useLocalDesktopUi) { + if (dataFeatures?.systemTrashEnabled || dataFeatures?.useLocalDesktop) { let newMenu = [...defaultMenu]; if (dataFeatures?.systemTrashEnabled) { newMenu = newMenu.filter((item) => item.key !== "trash"); } - if (dataFeatures?.useLocalDesktopUi) { + if (dataFeatures?.useLocalDesktop) { newMenu = newMenu.filter((item) => item.key !== "logout"); } setDefaultMenu([...newMenu]); diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-inline-search/menu-inline-search.spec.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-inline-search/menu-inline-search.spec.tsx index 7b8b724c1b..9c24da2e9e 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-inline-search/menu-inline-search.spec.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-inline-search/menu-inline-search.spec.tsx @@ -103,12 +103,12 @@ describe("menu-inline-search", () => { statusCode: 200, data: { systemTrashEnabled: true, - useLocalDesktopUi: false + useLocalDesktop: false } as IEnvFeatures } as IConnectionDefault; it("default menu should show logout and trash in default mode", () => { - dataFeaturesExample.data.useLocalDesktopUi = false; + dataFeaturesExample.data.useLocalDesktop = false; dataFeaturesExample.data.systemTrashEnabled = false; // usage ==> import * as useFetch from '../hooks/use-fetch'; diff --git a/starsky/starsky/clientapp/src/interfaces/IEnvFeatures.ts b/starsky/starsky/clientapp/src/interfaces/IEnvFeatures.ts index 00042dcdaf..891a0afd04 100644 --- a/starsky/starsky/clientapp/src/interfaces/IEnvFeatures.ts +++ b/starsky/starsky/clientapp/src/interfaces/IEnvFeatures.ts @@ -1,4 +1,4 @@ export interface IEnvFeatures { systemTrashEnabled: boolean; - useLocalDesktopUi: boolean; + useLocalDesktop: boolean; } diff --git a/starskydesktop/src/app/child-process/setup-child-process.ts b/starskydesktop/src/app/child-process/setup-child-process.ts index 131556a1db..b3d24ffcf6 100644 --- a/starskydesktop/src/app/child-process/setup-child-process.ts +++ b/starskydesktop/src/app/child-process/setup-child-process.ts @@ -55,7 +55,7 @@ export async function setupChildProcess() { app__tempFolder: tempFolder, app__appsettingspath: appSettingsPath, app__NoAccountLocalhost: "true", - app__UseLocalDesktopUi: "true", + app__UseLocalDesktop: "true", app__databaseConnection: databaseConnection, app__AccountRegisterDefaultRole: "Administrator", app__Verbose: !isPackaged() ? "true" : "false", From b678b2ea28ecb9e94b82db3b7be7ea09d861361f Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 19 Feb 2024 22:22:56 +0100 Subject: [PATCH 056/125] list in button is not supported --- .../clientapp/src/components/atoms/more-menu/more-menu.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/starsky/starsky/clientapp/src/components/atoms/more-menu/more-menu.tsx b/starsky/starsky/clientapp/src/components/atoms/more-menu/more-menu.tsx index 2abd358c76..65b8b8ccc1 100644 --- a/starsky/starsky/clientapp/src/components/atoms/more-menu/more-menu.tsx +++ b/starsky/starsky/clientapp/src/components/atoms/more-menu/more-menu.tsx @@ -44,7 +44,8 @@ const MoreMenu: React.FunctionComponent = ({ > {MessageMore} - + ); }; From daa8a102bbc95ecc5c79f7d546ce1404981f6efe Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 19 Feb 2024 22:29:06 +0100 Subject: [PATCH 057/125] move archive into seperate folder --- .../organisms/menu-archive/menu-archive.tsx | 1 + .../containers/{ => archive}/archive.spec.tsx | 2 +- .../containers/archive/archive.stories.tsx | 24 +++++++++++++++++++ .../src/containers/{ => archive}/archive.tsx | 18 +++++++------- .../archive-wrapper.spec.tsx | 2 +- .../src/contexts-wrappers/archive-wrapper.tsx | 2 +- 6 files changed, 37 insertions(+), 12 deletions(-) rename starsky/starsky/clientapp/src/containers/{ => archive}/archive.spec.tsx (94%) create mode 100644 starsky/starsky/clientapp/src/containers/archive/archive.stories.tsx rename starsky/starsky/clientapp/src/containers/{ => archive}/archive.tsx (65%) diff --git a/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx b/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx index 186f17012f..91dc76f904 100644 --- a/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx @@ -261,6 +261,7 @@ const MenuArchive: React.FunctionComponent = memo(() => { {/* onClick={() => allSelection()} */} + {select.length >= 1 ? ( <> { diff --git a/starsky/starsky/clientapp/src/containers/archive/archive.stories.tsx b/starsky/starsky/clientapp/src/containers/archive/archive.stories.tsx new file mode 100644 index 0000000000..2d97d7ff3a --- /dev/null +++ b/starsky/starsky/clientapp/src/containers/archive/archive.stories.tsx @@ -0,0 +1,24 @@ +import { MemoryRouter } from "react-router-dom"; +import { IArchiveProps } from "../../interfaces/IArchiveProps"; +import { Router } from "../../router-app/router-app"; +import Archive from "./archive"; + +export default { + title: "containers/archive" +}; + +export const Default = () => { + const item = {} as IArchiveProps; + + Router.navigate("?details=true&modal=false"); + + return ( + + + + ); +}; + +Default.story = { + name: "default" +}; diff --git a/starsky/starsky/clientapp/src/containers/archive.tsx b/starsky/starsky/clientapp/src/containers/archive/archive.tsx similarity index 65% rename from starsky/starsky/clientapp/src/containers/archive.tsx rename to starsky/starsky/clientapp/src/containers/archive/archive.tsx index 98e1bb6ad9..8f742b9038 100644 --- a/starsky/starsky/clientapp/src/containers/archive.tsx +++ b/starsky/starsky/clientapp/src/containers/archive/archive.tsx @@ -1,13 +1,13 @@ import React, { useEffect } from "react"; -import ArchivePagination from "../components/molecules/archive-pagination/archive-pagination"; -import Breadcrumb from "../components/molecules/breadcrumbs/breadcrumbs"; -import ColorClassFilter from "../components/molecules/color-class-filter/color-class-filter"; -import ItemListView from "../components/molecules/item-list-view/item-list-view"; -import ArchiveSidebar from "../components/organisms/archive-sidebar/archive-sidebar"; -import MenuArchive from "../components/organisms/menu-archive/menu-archive"; -import useLocation from "../hooks/use-location/use-location"; -import { IArchiveProps } from "../interfaces/IArchiveProps"; -import { URLPath } from "../shared/url-path"; +import ArchivePagination from "../../components/molecules/archive-pagination/archive-pagination"; +import Breadcrumb from "../../components/molecules/breadcrumbs/breadcrumbs"; +import ColorClassFilter from "../../components/molecules/color-class-filter/color-class-filter"; +import ItemListView from "../../components/molecules/item-list-view/item-list-view"; +import ArchiveSidebar from "../../components/organisms/archive-sidebar/archive-sidebar"; +import MenuArchive from "../../components/organisms/menu-archive/menu-archive"; +import useLocation from "../../hooks/use-location/use-location"; +import { IArchiveProps } from "../../interfaces/IArchiveProps"; +import { URLPath } from "../../shared/url-path"; function Archive(archive: Readonly) { const history = useLocation(); diff --git a/starsky/starsky/clientapp/src/contexts-wrappers/archive-wrapper.spec.tsx b/starsky/starsky/clientapp/src/contexts-wrappers/archive-wrapper.spec.tsx index 14cd47d7ce..84992b363c 100644 --- a/starsky/starsky/clientapp/src/contexts-wrappers/archive-wrapper.spec.tsx +++ b/starsky/starsky/clientapp/src/contexts-wrappers/archive-wrapper.spec.tsx @@ -1,6 +1,6 @@ import { render } from "@testing-library/react"; import React from "react"; -import * as Archive from "../containers/archive"; +import * as Archive from "../containers/archive/archive"; import * as Login from "../containers/login"; import * as Search from "../containers/search"; import * as Trash from "../containers/trash"; diff --git a/starsky/starsky/clientapp/src/contexts-wrappers/archive-wrapper.tsx b/starsky/starsky/clientapp/src/contexts-wrappers/archive-wrapper.tsx index a1aa0f5f69..35d159e796 100644 --- a/starsky/starsky/clientapp/src/contexts-wrappers/archive-wrapper.tsx +++ b/starsky/starsky/clientapp/src/contexts-wrappers/archive-wrapper.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from "react"; import Preloader from "../components/atoms/preloader/preloader"; -import Archive from "../containers/archive"; +import Archive from "../containers/archive/archive"; import { Login } from "../containers/login"; import Search from "../containers/search"; import Trash from "../containers/trash"; From aee02d1a704a78a3f2ab05cd0f77ee762304c49c Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 19 Feb 2024 22:32:56 +0100 Subject: [PATCH 058/125] change path --- .../clientapp/src/containers/archive/archive.stories.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/starsky/starsky/clientapp/src/containers/archive/archive.stories.tsx b/starsky/starsky/clientapp/src/containers/archive/archive.stories.tsx index 2d97d7ff3a..550628bdee 100644 --- a/starsky/starsky/clientapp/src/containers/archive/archive.stories.tsx +++ b/starsky/starsky/clientapp/src/containers/archive/archive.stories.tsx @@ -8,13 +8,17 @@ export default { }; export const Default = () => { - const item = {} as IArchiveProps; + const archive = { + colorClassUsage: [1], + colorClassActiveList: [1], + fileIndexItems: [{ fileName: "test", filePath: "/test.jpg", colorClass: 1 }] + } as IArchiveProps; Router.navigate("?details=true&modal=false"); return ( - + ); }; From ead5d8ba966ea38cfd2803dd1938097a4b2ec373 Mon Sep 17 00:00:00 2001 From: Dion Date: Tue, 20 Feb 2024 17:50:14 +0100 Subject: [PATCH 059/125] ignore if collections false && add removeal of duplicates --- .../Service/OpenEditorPreflight.cs | 15 +- .../Service/OpenEditorPreflightTests.cs | 153 +++++++++++++++++- 2 files changed, 162 insertions(+), 6 deletions(-) diff --git a/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs b/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs index 38195da794..3b6c606fa3 100644 --- a/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs +++ b/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs @@ -37,7 +37,7 @@ public async Task> PreflightAsync( List inputFilePaths, bool collections) { var fileIndexItemList = await GetObjectsToOpenFromDatabase(inputFilePaths, collections); - fileIndexItemList = GroupByFileCollectionName(fileIndexItemList); + fileIndexItemList = GroupByFileCollectionName(fileIndexItemList, collections); var subPathAndImageFormatList = new List(); @@ -119,12 +119,18 @@ internal async Task> GetObjectsToOpenFromDatabase( fileIndexList.Add(fileIndexItem); } - return fileIndexList; + return fileIndexList.DistinctBy(p => p.FilePath).ToList(); } internal List GroupByFileCollectionName( - IEnumerable fileIndexInputList) + IEnumerable fileIndexInputList, bool collections = true) { + // Skip if no collections, no need to filter on the right file + if ( !collections ) + { + return fileIndexInputList.ToList(); + } + if ( _appSettings.DesktopCollectionsOpen is CollectionsOpenType.RawJpegMode.Default ) { _appSettings.DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Jpeg; @@ -169,6 +175,7 @@ or ExtensionRolesHelper.ImageFormat.gif toOpenResultList.Add(fileIndexItem); } - return toOpenResultList; + // could be that the same file is in multiple collections + return toOpenResultList.DistinctBy(p => p.FilePath).ToList(); } } diff --git a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs index ec51210a6e..b5ec40f719 100644 --- a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs +++ b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorPreflightTests.cs @@ -213,14 +213,86 @@ public async Task GetObjectsToOpenFromDatabase_SkipXmpSidecar() } [TestMethod] - public async Task GetObjectsToOpenFromDatabase_OkStatus() + public async Task GetObjectsToOpenFromDatabase_ChangeDefaultToOkStatus() { // Arrange var queryStub = new FakeIQuery(new List { new FileIndexItem("/test.mp4") { - ImageFormat = ExtensionRolesHelper.ImageFormat.mp4 + ImageFormat = ExtensionRolesHelper.ImageFormat.mp4, + Status = FileIndexItem.ExifStatus.Default // difference here! + } + }); + var appSettingsStub = new AppSettings(); + var storageStub = new FakeIStorage(new List(), + new List { "/test.mp4" }); + + // Assuming you have appropriate setup for your test case + var inputFilePaths = new List { "/test.mp4" }; + const bool collections = false; + + var openEditorPreflight = new OpenEditorPreflight(queryStub, appSettingsStub, + new FakeSelectorStorage(storageStub), new FakeIWebLogger()); + + // Act + var result = + await openEditorPreflight.GetObjectsToOpenFromDatabase(inputFilePaths, collections); + + // Change the status to Ok + Assert.AreEqual(1, result.Count); + Assert.AreEqual("/test.mp4", result[0].FilePath); + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, result[0].Status); + } + + [TestMethod] + public async Task GetObjectsToOpenFromDatabase_Duplicates() + { + // Arrange + var queryStub = new FakeIQuery(new List + { + new FileIndexItem("/test.mp4") + { + ImageFormat = ExtensionRolesHelper.ImageFormat.mp4, + Status = FileIndexItem.ExifStatus.Ok + }, + new FileIndexItem("/test.mp4") + { + ImageFormat = ExtensionRolesHelper.ImageFormat.mp4, + Status = FileIndexItem.ExifStatus.Ok // yes duplicates + } + }); + var appSettingsStub = new AppSettings(); + var storageStub = new FakeIStorage(new List(), + new List { "/test.mp4" }); + + // Assuming you have appropriate setup for your test case + var inputFilePaths = new List { "/test.mp4" }; + const bool collections = false; + + var openEditorPreflight = new OpenEditorPreflight(queryStub, appSettingsStub, + new FakeSelectorStorage(storageStub), new FakeIWebLogger()); + + // Act + var result = + await openEditorPreflight.GetObjectsToOpenFromDatabase(inputFilePaths, collections); + + // removed duplicates + Assert.AreEqual(1, result.Count); + Assert.AreEqual("/test.mp4", result[0].FilePath); + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, result[0].Status); + } + + [TestMethod] + public async Task GetObjectsToOpenFromDatabase_ChangeOkAndSameToOkStatus() + { + // Arrange + var queryStub = new FakeIQuery(new List + { + new FileIndexItem("/test.mp4") + { + ImageFormat = ExtensionRolesHelper.ImageFormat.mp4, + Status = FileIndexItem.ExifStatus.OkAndSame // difference here! } }); var appSettingsStub = new AppSettings(); @@ -405,4 +477,81 @@ public void GroupByFileCollectionName_ReturnsCorrectList_WhenAppSettingsIsOtherT Assert.AreEqual(ExtensionRolesHelper.ImageFormat.mp4, collection1?.ImageFormat); } + + [TestMethod] + public void GroupByFileCollectionName_XmpFile_CollectionsFalse() + { + // Arrange + var query = new FakeIQuery(); // You can mock IQuery if needed + var appSettings = + new AppSettings { DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Raw }; + var iStorage = new FakeIStorage(); + var preflight = + new OpenEditorPreflight(query, appSettings, new FakeSelectorStorage(iStorage), + new FakeIWebLogger()); + + var fileIndexList = new List + { + new FileIndexItem + { + FileName = "collection1.xmp", + ImageFormat = ExtensionRolesHelper.ImageFormat.xmp + }, + new FileIndexItem + { + FileName = "collection1.jpg", + ImageFormat = ExtensionRolesHelper.ImageFormat.jpg + } + }; + + // Act + // Collection is disabled + var result = preflight.GroupByFileCollectionName(fileIndexList, false); + + // Assert + Assert.AreEqual(2, result.Count); + + var collection1Xmp = result.Find(p => p.FileName == "collection1.xmp"); + var collection1Jpg = result.Find(p => p.FileName == "collection1.jpg"); + + Assert.AreEqual(ExtensionRolesHelper.ImageFormat.xmp, collection1Xmp?.ImageFormat); + Assert.AreEqual(ExtensionRolesHelper.ImageFormat.jpg, collection1Jpg?.ImageFormat); + } + + [TestMethod] + public void GroupByFileCollectionName_Duplicates() + { + // Arrange + var query = new FakeIQuery(); // You can mock IQuery if needed + var appSettings = + new AppSettings { DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Raw }; + var iStorage = new FakeIStorage(); + var preflight = + new OpenEditorPreflight(query, appSettings, new FakeSelectorStorage(iStorage), + new FakeIWebLogger()); + + var fileIndexList = new List + { + new FileIndexItem + { + FileName = "collection1.jpg", // duplicate + ImageFormat = ExtensionRolesHelper.ImageFormat.jpg + }, + new FileIndexItem + { + FileName = "collection1.jpg", // duplicate + ImageFormat = ExtensionRolesHelper.ImageFormat.jpg + } + }; + + // Act + var result = preflight.GroupByFileCollectionName(fileIndexList); + + // Assert + Assert.AreEqual(1, result.Count); + + var collection1 = result.Find(p => p.FileCollectionName == "collection1"); + + Assert.AreEqual(ExtensionRolesHelper.ImageFormat.jpg, collection1?.ImageFormat); + } } From 219568c4d5c4fbe3e7de8b92cd8424f93db8ec4c Mon Sep 17 00:00:00 2001 From: Dion Date: Tue, 20 Feb 2024 17:50:24 +0100 Subject: [PATCH 060/125] add comment --- starsky/starsky.foundation.database/Helpers/Duplicate.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/starsky/starsky.foundation.database/Helpers/Duplicate.cs b/starsky/starsky.foundation.database/Helpers/Duplicate.cs index 4811732eda..552e2b1970 100644 --- a/starsky/starsky.foundation.database/Helpers/Duplicate.cs +++ b/starsky/starsky.foundation.database/Helpers/Duplicate.cs @@ -16,11 +16,12 @@ public Duplicate(IQuery query) } /// - /// Check and remove duplicate + /// Check and remove duplicate from database /// /// /// - public async Task> RemoveDuplicateAsync(List databaseSubFolderList) + public async Task> RemoveDuplicateAsync( + List databaseSubFolderList) { // Get a list of duplicate items var duplicateItemsByFilePath = databaseSubFolderList.GroupBy(item => item.FilePath) @@ -37,6 +38,7 @@ public async Task> RemoveDuplicateAsync(List await _query.RemoveItemAsync(duplicateItems[i]); } } + return databaseSubFolderList; } } From 6a6ce5ff3a56937bdeb262db591c2e5a6dcc037c Mon Sep 17 00:00:00 2001 From: Dion Date: Tue, 20 Feb 2024 18:01:23 +0100 Subject: [PATCH 061/125] move tests / rename starskycore => starsky.project.web --- .../Helpers/OperatingSystemHelper.cs | 12 +- .../Helpers/WindowsSetFileAssociations.cs | 32 +- .../starsky.foundation.native.csproj | 3 +- .../Helpers/ReadAppSettings.cs | 2 +- .../Helpers/StringHelper.cs | 7 +- .../Helpers/EnumHelper.cs | 2 +- .../ExcludeFromCoverageAttribute.cs | 9 + .../Helpers/MimeHelper.cs | 2 +- .../Helpers/PortProgramHelper.cs | 22 +- .../Helpers/ToSQL.cs_debug | 2 +- .../ViewModels/ArchiveViewModel.cs | 2 +- .../ViewModels/EnvFeaturesViewModel.cs | 2 +- .../ViewModels/HealthView.cs | 2 +- .../ViewModels/MetricsDebugViewModel.cs | 2 +- .../ViewModels/SyncViewModel.cs | 4 +- .../starsky.project.web.csproj} | 1 + starsky/starsky.sln | 2 +- .../Controllers/AllowedTypesController.cs | 9 +- .../AppSettingsFeaturesController.cs | 2 +- starsky/starsky/Controllers/DiskController.cs | 30 +- .../Controllers/DownloadPhotoController.cs | 2 +- .../starsky/Controllers/ExportController.cs | 2 +- .../starsky/Controllers/HealthController.cs | 10 +- .../starsky/Controllers/IndexController.cs | 2 +- .../Controllers/MetricsDebugController.cs | 7 +- .../Controllers/ThumbnailController.cs | 68 +- starsky/starsky/Program.cs | 6 +- starsky/starsky/starsky.csproj | 2 +- starsky/starskybusinesslogic/readme.md | 115 +-- .../ExcludeFromCoverageAttribute.cs | 7 - .../AppSettingsFeaturesControllerTest.cs | 2 +- .../Controllers/DiskControllerTest.cs | 210 +++-- .../Controllers/ExportControllerTest.cs | 1 - .../Controllers/GeoControllerTest.cs | 1 - .../Controllers/HealthControllerTest.cs | 2 +- .../Controllers/ImportControllerTest.cs | 1 - .../Controllers/IndexControllerTest.cs | 56 +- .../Controllers/MetaReplaceControllerTest.cs | 1 - .../Controllers/MetaUpdateControllerTest.cs | 1 - .../Controllers/MetricsDebugControllerTest.cs | 4 +- .../Controllers/PublishControllerTest.cs | 1 - .../Controllers/UploadControllerTest.cs | 272 ++++--- .../{Models => FakeMocks}/FakeExifTool.cs | 27 +- starsky/starskytest/Helpers/MimeHelperTest.cs | 46 -- .../Models/Account/CredentialTypeTest.cs | 27 - .../Models/Account/PermissionTest.cs | 25 - .../Models/Account/RolePermissionTest.cs | 24 - .../Models/Account/UserRoleTest.cs | 23 - .../Models/FolderOrFileModelTest.cs | 21 - .../starskytest/Models/SearchViewModelTest.cs | 261 ------- .../ViewModels/ArchiveViewModelTest.cs | 47 +- .../ViewModels/SyncViewModelTest.cs | 13 +- .../Services/GeoLocationWriteTest.cs | 27 +- .../UpdateImportTransformationsTest.cs | 1 - .../Services/ImportTest.cs | 3 +- .../Services/ImportTest_InMemoryDb.cs | 155 ++-- .../Services/MetaUpdateServiceTest.cs | 1 - .../Services/MoveToTrashServiceTest.cs | 270 +++---- .../Services/WebHtmlPublishServiceTest.cs | 4 +- .../Interfaces/IUserManagerTest.cs | 27 +- .../EntityFrameworkExtensionsTest.cs | 78 +- .../Helpers/BreadcrumbHelperTest.cs | 7 +- .../Models/Account/CredentialTest.cs | 18 +- .../Models/Account/CredentialTypeTest.cs | 19 +- .../Models/Account/PermissionTest.cs | 21 + .../Models/Account/RolePermissionTest.cs | 14 +- .../Models/Account/RoleTest.cs | 11 +- .../Models/Account/UserRoleTest.cs | 13 +- .../Models/Account/UserTest.cs | 2 +- .../Models/FileIndexItemTest.cs | 264 ++++--- .../QueryTest/QueryTest.cs | 2 +- .../Helpers/HttpClientHelperTest.cs | 156 ++-- .../WindowsSetFileAssociationsUnixTests.cs | 2 +- .../Helpers/ArgsHelperTest.cs | 361 ++++----- .../Helpers/Base64HelperTest.cs | 3 +- .../Helpers/EnumHelperTest.cs | 115 --- .../Helpers/PathHelper2Test.cs | 15 +- .../Helpers/PortProgramHelperTest.cs | 175 ----- .../Services/ReadMeta_ExifReadTest.cs | 721 ++++++++++-------- .../ViewModels/SearchViewModelTest.cs | 329 +++++++- .../Models/FolderOrFileModelTest.cs | 18 + .../Helpers/EnumHelperTest.cs | 111 +++ .../Helpers/ExifToolCmdHelperTest.cs | 391 +++++----- .../Services/ExifCopyTest.cs | 85 ++- .../Helpers/FilenameHelpersTest.cs | 2 +- .../Helpers/MimeHelperTest.cs | 48 ++ .../Helpers/PortProgramHelperTest.cs | 184 +++++ starsky/starskytest/starskytest.csproj | 2 +- 88 files changed, 2609 insertions(+), 2482 deletions(-) rename starsky/{starsky.foundation.platform => starsky.foundation.writemeta}/Helpers/EnumHelper.cs (92%) create mode 100644 starsky/starsky.project.web/Attributes/ExcludeFromCoverageAttribute.cs rename starsky/{starskycore => starsky.project.web}/Helpers/MimeHelper.cs (99%) rename starsky/{starsky.foundation.platform => starsky.project.web}/Helpers/PortProgramHelper.cs (69%) rename starsky/{starskycore => starsky.project.web}/Helpers/ToSQL.cs_debug (96%) rename starsky/{starskycore => starsky.project.web}/ViewModels/ArchiveViewModel.cs (97%) rename starsky/{starskycore => starsky.project.web}/ViewModels/EnvFeaturesViewModel.cs (87%) rename starsky/{starskycore => starsky.project.web}/ViewModels/HealthView.cs (93%) rename starsky/{starskycore => starsky.project.web}/ViewModels/MetricsDebugViewModel.cs (66%) rename starsky/{starskycore => starsky.project.web}/ViewModels/SyncViewModel.cs (87%) rename starsky/{starskycore/starskycore.csproj => starsky.project.web/starsky.project.web.csproj} (93%) delete mode 100644 starsky/starskycore/Attributes/ExcludeFromCoverageAttribute.cs rename starsky/starskytest/{Models => FakeMocks}/FakeExifTool.cs (60%) delete mode 100644 starsky/starskytest/Helpers/MimeHelperTest.cs delete mode 100644 starsky/starskytest/Models/Account/CredentialTypeTest.cs delete mode 100644 starsky/starskytest/Models/Account/PermissionTest.cs delete mode 100644 starsky/starskytest/Models/Account/RolePermissionTest.cs delete mode 100644 starsky/starskytest/Models/Account/UserRoleTest.cs delete mode 100644 starsky/starskytest/Models/FolderOrFileModelTest.cs delete mode 100644 starsky/starskytest/Models/SearchViewModelTest.cs rename starsky/starskytest/{ => starsky.foundation.accountmanagement}/Interfaces/IUserManagerTest.cs (52%) rename starsky/starskytest/{ => starsky.foundation.database}/Extensions/EntityFrameworkExtensionsTest.cs (77%) rename starsky/starskytest/{ => starsky.foundation.database}/Helpers/BreadcrumbHelperTest.cs (94%) rename starsky/starskytest/{ => starsky.foundation.database}/Models/Account/CredentialTest.cs (63%) create mode 100644 starsky/starskytest/starsky.foundation.database/Models/Account/PermissionTest.cs rename starsky/starskytest/{ => starsky.foundation.database}/Models/Account/RoleTest.cs (66%) rename starsky/starskytest/{ => starsky.foundation.database}/Models/Account/UserTest.cs (89%) rename starsky/starskytest/{ => starsky.foundation.http}/Helpers/HttpClientHelperTest.cs (80%) rename starsky/starskytest/{ => starsky.foundation.platform}/Helpers/Base64HelperTest.cs (94%) delete mode 100644 starsky/starskytest/starsky.foundation.platform/Helpers/EnumHelperTest.cs delete mode 100644 starsky/starskytest/starsky.foundation.platform/Helpers/PortProgramHelperTest.cs create mode 100644 starsky/starskytest/starsky.foundation.storage/Models/FolderOrFileModelTest.cs create mode 100644 starsky/starskytest/starsky.foundation.writemeta/Helpers/EnumHelperTest.cs rename starsky/starskytest/{ => starsky.project.web}/Helpers/FilenameHelpersTest.cs (98%) create mode 100644 starsky/starskytest/starsky.project.web/Helpers/MimeHelperTest.cs create mode 100644 starsky/starskytest/starsky.project.web/Helpers/PortProgramHelperTest.cs diff --git a/starsky/starsky.foundation.native/Helpers/OperatingSystemHelper.cs b/starsky/starsky.foundation.native/Helpers/OperatingSystemHelper.cs index efccca4dfc..7bbf979784 100644 --- a/starsky/starsky.foundation.native/Helpers/OperatingSystemHelper.cs +++ b/starsky/starsky.foundation.native/Helpers/OperatingSystemHelper.cs @@ -11,20 +11,30 @@ public static OSPlatform GetPlatform() internal delegate bool IsOsPlatformDelegate(OSPlatform osPlatform); + /// + /// Used to make the function testable + /// + /// Delegate to know what the OS is + /// Runtime OS internal static OSPlatform GetPlatformInternal(IsOsPlatformDelegate isOsPlatformDelegate) { if ( isOsPlatformDelegate(OSPlatform.Windows) ) { return OSPlatform.Windows; } + if ( isOsPlatformDelegate(OSPlatform.OSX) ) { return OSPlatform.OSX; } + if ( isOsPlatformDelegate(OSPlatform.Linux) ) { return OSPlatform.Linux; } - return isOsPlatformDelegate(OSPlatform.FreeBSD) ? OSPlatform.FreeBSD : OSPlatform.Create("Unknown"); + + return isOsPlatformDelegate(OSPlatform.FreeBSD) + ? OSPlatform.FreeBSD + : OSPlatform.Create("Unknown"); } } diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs index 33e547fdee..faa4292a0d 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociations.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Microsoft.Win32; +using starsky.foundation.platform.Helpers; namespace starsky.foundation.native.OpenApplicationNative.Helpers; @@ -17,6 +18,9 @@ public class FileAssociation [SuppressMessage("ReSharper", "IdentifierTypo")] [SuppressMessage("ReSharper", "InconsistentNaming")] [SuppressMessage("Performance", "CA1806:Do not ignore method results")] +[SuppressMessage("Interoperability", "SYSLIB1054:Use \'LibraryImportAttribute\' " + + "instead of \'DllImportAttribute\' to generate P/Invoke " + + "marshalling code at compile time")] public static class WindowsSetFileAssociations { /// @@ -29,9 +33,6 @@ public static class WindowsSetFileAssociations /// /// [DllImport("Shell32.dll")] - [SuppressMessage("Interoperability", "SYSLIB1054:Use \'LibraryImportAttribute\' " + - "instead of \'DllImportAttribute\' to generate P/Invoke " + - "marshalling code at compile time")] private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2); private const int SHCNE_ASSOCCHANGED = 0x8000000; @@ -57,27 +58,34 @@ public static bool EnsureAssociationsSet(params FileAssociation[] associations) return madeChanges; } - internal static bool SetAssociation(string extension, string progId, string fileTypeDescription, + private static bool SetAssociation(string extension, string progId, string fileTypeDescription, string applicationFilePath) { var madeChanges = false; - madeChanges |= SetKeyDefaultValue(@"Software\Classes\" + extension, progId); - madeChanges |= SetKeyDefaultValue(@"Software\Classes\" + progId, fileTypeDescription); - madeChanges |= SetKeyDefaultValue($@"Software\Classes\{progId}\shell\open\command", + madeChanges |= SetKeyValue(@"Software\Classes\" + extension, progId); + madeChanges |= SetKeyValue(@"Software\Classes\" + progId, fileTypeDescription); + madeChanges |= SetKeyValue($@"Software\Classes\{progId}\shell\open\command", "\"" + applicationFilePath + "\" \"%1\""); return madeChanges; } - internal static bool SetKeyDefaultValue(string keyPath, string value) + internal static bool SetKeyValue(string keyPath, string value) { if ( !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ) { return false; } - using var key = Registry.CurrentUser.CreateSubKey(keyPath); - if ( key.GetValue(null) as string == value ) return false; - key.SetValue(null, value); - return true; + return RetryHelper.Do(SetValue, TimeSpan.FromSeconds(1), 2); + + // Can sometimes have: System.IO.IOException: Illegal operation attempted + // on a registry key that has been marked for deletion + bool SetValue() + { + using var key = Registry.CurrentUser.CreateSubKey(keyPath); + if ( key.GetValue(null) as string == value ) return false; + key.SetValue(null, value); + return true; + } } } diff --git a/starsky/starsky.foundation.native/starsky.foundation.native.csproj b/starsky/starsky.foundation.native/starsky.foundation.native.csproj index 957ee3721b..3d1dd59be7 100644 --- a/starsky/starsky.foundation.native/starsky.foundation.native.csproj +++ b/starsky/starsky.foundation.native/starsky.foundation.native.csproj @@ -12,6 +12,7 @@ - + + diff --git a/starsky/starsky.foundation.platform/Helpers/ReadAppSettings.cs b/starsky/starsky.foundation.platform/Helpers/ReadAppSettings.cs index 664f357657..a386464bdc 100644 --- a/starsky/starsky.foundation.platform/Helpers/ReadAppSettings.cs +++ b/starsky/starsky.foundation.platform/Helpers/ReadAppSettings.cs @@ -8,7 +8,7 @@ namespace starsky.foundation.platform.Helpers; public static class ReadAppSettings { - internal static async Task Read(string path) + public static async Task Read(string path) { if ( !File.Exists(path) ) { diff --git a/starsky/starsky.foundation.platform/Helpers/StringHelper.cs b/starsky/starsky.foundation.platform/Helpers/StringHelper.cs index eeec46af9c..dadc18abdc 100644 --- a/starsky/starsky.foundation.platform/Helpers/StringHelper.cs +++ b/starsky/starsky.foundation.platform/Helpers/StringHelper.cs @@ -4,10 +4,11 @@ public static class StringHelper { public static string AsciiNullReplacer(string newStringValue) { - return ( newStringValue == "\\0" || newStringValue == "\\\\0" ) ? string.Empty : newStringValue; + return ( newStringValue == "\\0" || newStringValue == "\\\\0" ) + ? string.Empty + : newStringValue; } - public static readonly string AsciiNullChar = "\\\\0"; - + public const string AsciiNullChar = @"\\0"; } } diff --git a/starsky/starsky.foundation.platform/Helpers/EnumHelper.cs b/starsky/starsky.foundation.writemeta/Helpers/EnumHelper.cs similarity index 92% rename from starsky/starsky.foundation.platform/Helpers/EnumHelper.cs rename to starsky/starsky.foundation.writemeta/Helpers/EnumHelper.cs index e329bd7fa3..b799c3daaf 100644 --- a/starsky/starsky.foundation.platform/Helpers/EnumHelper.cs +++ b/starsky/starsky.foundation.writemeta/Helpers/EnumHelper.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Reflection; -namespace starsky.foundation.platform.Helpers +namespace starsky.foundation.writemeta.Helpers { public static class EnumHelper { diff --git a/starsky/starsky.project.web/Attributes/ExcludeFromCoverageAttribute.cs b/starsky/starsky.project.web/Attributes/ExcludeFromCoverageAttribute.cs new file mode 100644 index 0000000000..583bf9205a --- /dev/null +++ b/starsky/starsky.project.web/Attributes/ExcludeFromCoverageAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace starsky.project.web.Attributes +{ + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor)] + public sealed class ExcludeFromCoverageAttribute : Attribute + { + } +} diff --git a/starsky/starskycore/Helpers/MimeHelper.cs b/starsky/starsky.project.web/Helpers/MimeHelper.cs similarity index 99% rename from starsky/starskycore/Helpers/MimeHelper.cs rename to starsky/starsky.project.web/Helpers/MimeHelper.cs index 8d3c9ac17a..d1bcb9363c 100644 --- a/starsky/starskycore/Helpers/MimeHelper.cs +++ b/starsky/starsky.project.web/Helpers/MimeHelper.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.IO; -namespace starskycore.Helpers +namespace starsky.project.web.Helpers { public static class MimeHelper { diff --git a/starsky/starsky.foundation.platform/Helpers/PortProgramHelper.cs b/starsky/starsky.project.web/Helpers/PortProgramHelper.cs similarity index 69% rename from starsky/starsky.foundation.platform/Helpers/PortProgramHelper.cs rename to starsky/starsky.project.web/Helpers/PortProgramHelper.cs index 292e1e5b75..7b64e6c1d7 100644 --- a/starsky/starsky.foundation.platform/Helpers/PortProgramHelper.cs +++ b/starsky/starsky.project.web/Helpers/PortProgramHelper.cs @@ -2,13 +2,18 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading.Tasks; +using starsky.foundation.platform.Helpers; -namespace starsky.foundation.platform.Helpers; +[assembly: InternalsVisibleTo("starskytest")] + +namespace starsky.project.web.Helpers; public static class PortProgramHelper { - public static async Task SetEnvPortAspNetUrlsAndSetDefault(string[] args, string appSettingsPath) + public static async Task SetEnvPortAspNetUrlsAndSetDefault(string[] args, + string appSettingsPath) { if ( await SkipForAppSettingsJsonFile(appSettingsPath) ) { @@ -23,14 +28,14 @@ internal static async Task SkipForAppSettingsJsonFile(string appSettingsPa { var appContainer = await ReadAppSettings.Read(appSettingsPath); if ( appContainer?.Kestrel?.Endpoints?.Http?.Url == null && - appContainer?.Kestrel?.Endpoints?.Https?.Url == null ) + appContainer?.Kestrel?.Endpoints?.Https?.Url == null ) { return false; } Console.WriteLine("Kestrel Endpoints are set in appsettings.json, " + - "this results in skip setting the PORT and default " + - "ASPNETCORE_URLS environment variable"); + "this results in skip setting the PORT and default " + + "ASPNETCORE_URLS environment variable"); return true; } @@ -40,7 +45,7 @@ internal static void SetEnvPortAspNetUrls(IEnumerable args) var portString = Environment.GetEnvironmentVariable("PORT"); if ( args.Contains("--urls") || string.IsNullOrEmpty(portString) - || !int.TryParse(portString, out var port) ) return; + || !int.TryParse(portString, out var port) ) return; SetEnvironmentVariableForPort(port); } @@ -49,7 +54,7 @@ internal static void SetEnvPortAspNetUrls(IEnumerable args) private static void SetEnvironmentVariableForPort(int port) { Console.WriteLine($"Set port from environment variable: {port} " + - $"\nPro tip: Its recommended to use a https proxy like nginx or traefik"); + $"\nPro tip: Its recommended to use a https proxy like nginx or traefik"); Environment.SetEnvironmentVariable("ASPNETCORE_URLS", $"http://*:{port}"); } @@ -57,6 +62,7 @@ internal static void SetDefaultAspNetCoreUrls(IEnumerable args) { var aspNetCoreUrls = Environment.GetEnvironmentVariable("ASPNETCORE_URLS"); if ( args.Contains("--urls") || !string.IsNullOrEmpty(aspNetCoreUrls) ) return; - Environment.SetEnvironmentVariable("ASPNETCORE_URLS", "http://localhost:4000;https://localhost:4001"); + Environment.SetEnvironmentVariable("ASPNETCORE_URLS", + "http://localhost:4000;https://localhost:4001"); } } diff --git a/starsky/starskycore/Helpers/ToSQL.cs_debug b/starsky/starsky.project.web/Helpers/ToSQL.cs_debug similarity index 96% rename from starsky/starskycore/Helpers/ToSQL.cs_debug rename to starsky/starsky.project.web/Helpers/ToSQL.cs_debug index 1c687c6264..f8af8d6248 100644 --- a/starsky/starskycore/Helpers/ToSQL.cs_debug +++ b/starsky/starsky.project.web/Helpers/ToSQL.cs_debug @@ -4,7 +4,7 @@ using System.Reflection; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; -namespace starskycore.Helpers +namespace starsky.project.web.Helpers { public static class ToSqlExtension { diff --git a/starsky/starskycore/ViewModels/ArchiveViewModel.cs b/starsky/starsky.project.web/ViewModels/ArchiveViewModel.cs similarity index 97% rename from starsky/starskycore/ViewModels/ArchiveViewModel.cs rename to starsky/starsky.project.web/ViewModels/ArchiveViewModel.cs index 2c86a3a9b0..e9fccb8eff 100644 --- a/starsky/starskycore/ViewModels/ArchiveViewModel.cs +++ b/starsky/starsky.project.web/ViewModels/ArchiveViewModel.cs @@ -3,7 +3,7 @@ using starsky.foundation.database.Models; using starsky.foundation.platform.Helpers; -namespace starskycore.ViewModels +namespace starsky.project.web.ViewModels { [SuppressMessage("Performance", "CA1822:Mark members as static")] public sealed class ArchiveViewModel diff --git a/starsky/starskycore/ViewModels/EnvFeaturesViewModel.cs b/starsky/starsky.project.web/ViewModels/EnvFeaturesViewModel.cs similarity index 87% rename from starsky/starskycore/ViewModels/EnvFeaturesViewModel.cs rename to starsky/starsky.project.web/ViewModels/EnvFeaturesViewModel.cs index 806c84f78a..60a8942cf1 100644 --- a/starsky/starskycore/ViewModels/EnvFeaturesViewModel.cs +++ b/starsky/starsky.project.web/ViewModels/EnvFeaturesViewModel.cs @@ -1,4 +1,4 @@ -namespace starskycore.ViewModels; +namespace starsky.project.web.ViewModels; public class EnvFeaturesViewModel { diff --git a/starsky/starskycore/ViewModels/HealthView.cs b/starsky/starsky.project.web/ViewModels/HealthView.cs similarity index 93% rename from starsky/starskycore/ViewModels/HealthView.cs rename to starsky/starsky.project.web/ViewModels/HealthView.cs index e248dd584e..32929e176f 100644 --- a/starsky/starskycore/ViewModels/HealthView.cs +++ b/starsky/starsky.project.web/ViewModels/HealthView.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -namespace starskycore.ViewModels +namespace starsky.project.web.ViewModels { public sealed class HealthView { diff --git a/starsky/starskycore/ViewModels/MetricsDebugViewModel.cs b/starsky/starsky.project.web/ViewModels/MetricsDebugViewModel.cs similarity index 66% rename from starsky/starskycore/ViewModels/MetricsDebugViewModel.cs rename to starsky/starsky.project.web/ViewModels/MetricsDebugViewModel.cs index 83123d93bd..93293b5085 100644 --- a/starsky/starskycore/ViewModels/MetricsDebugViewModel.cs +++ b/starsky/starsky.project.web/ViewModels/MetricsDebugViewModel.cs @@ -1,4 +1,4 @@ -namespace starskycore.ViewModels; +namespace starsky.project.web.ViewModels; public class MetricsDebugViewModel { diff --git a/starsky/starskycore/ViewModels/SyncViewModel.cs b/starsky/starsky.project.web/ViewModels/SyncViewModel.cs similarity index 87% rename from starsky/starskycore/ViewModels/SyncViewModel.cs rename to starsky/starsky.project.web/ViewModels/SyncViewModel.cs index 2f33ba4bf9..0e3c341b7c 100644 --- a/starsky/starskycore/ViewModels/SyncViewModel.cs +++ b/starsky/starsky.project.web/ViewModels/SyncViewModel.cs @@ -1,7 +1,7 @@ -using starsky.foundation.database.Models; using System.Text.Json.Serialization; +using starsky.foundation.database.Models; -namespace starskycore.ViewModels +namespace starsky.project.web.ViewModels { public sealed class SyncViewModel { diff --git a/starsky/starskycore/starskycore.csproj b/starsky/starsky.project.web/starsky.project.web.csproj similarity index 93% rename from starsky/starskycore/starskycore.csproj rename to starsky/starsky.project.web/starsky.project.web.csproj index 04ae30444c..91466ff82f 100644 --- a/starsky/starskycore/starskycore.csproj +++ b/starsky/starsky.project.web/starsky.project.web.csproj @@ -10,6 +10,7 @@ SYSTEM_TEXT_ENABLED Full enable + starsky.project.web diff --git a/starsky/starsky.sln b/starsky/starsky.sln index 0c2506287d..ef8b5bbfa1 100644 --- a/starsky/starsky.sln +++ b/starsky/starsky.sln @@ -13,7 +13,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "starskywebhtmlcli", "starsk EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "starskygeocli", "starskygeocli\starskygeocli.csproj", "{EF96F7C8-4832-4606-8F5C-B1423FEE83B8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "starskycore", "starskycore\starskycore.csproj", "{A90751E7-2F4D-44AA-8507-DDE5F980DBBB}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "starsky.project.web", "starsky.project.web\starsky.project.web.csproj", "{A90751E7-2F4D-44AA-8507-DDE5F980DBBB}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "starskywebftpcli", "starskywebftpcli\starskywebftpcli.csproj", "{F8CE092D-F296-4B04-B013-EE5FCD4A8B3B}" EndProject diff --git a/starsky/starsky/Controllers/AllowedTypesController.cs b/starsky/starsky/Controllers/AllowedTypesController.cs index a695ef484a..b60eb91e9f 100644 --- a/starsky/starsky/Controllers/AllowedTypesController.cs +++ b/starsky/starsky/Controllers/AllowedTypesController.cs @@ -3,14 +3,13 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using starsky.foundation.platform.Helpers; -using starskycore.Helpers; +using starsky.project.web.Helpers; namespace starsky.Controllers { [Authorize] public sealed class AllowedTypesController : Controller { - /// /// A (string) list of allowed MIME-types ExtensionSyncSupportedList /// @@ -22,7 +21,8 @@ public sealed class AllowedTypesController : Controller [Produces("application/json")] public IActionResult AllowedTypesMimetypeSync() { - var mimeTypes = ExtensionRolesHelper.ExtensionSyncSupportedList.Select(MimeHelper.GetMimeType).ToHashSet(); + var mimeTypes = ExtensionRolesHelper.ExtensionSyncSupportedList + .Select(MimeHelper.GetMimeType).ToHashSet(); return Json(mimeTypes); } @@ -38,7 +38,8 @@ public IActionResult AllowedTypesMimetypeSync() [Produces("application/json")] public IActionResult AllowedTypesMimetypeSyncThumb() { - var mimeTypes = ExtensionRolesHelper.ExtensionThumbSupportedList.Select(MimeHelper.GetMimeType).ToHashSet(); + var mimeTypes = ExtensionRolesHelper.ExtensionThumbSupportedList + .Select(MimeHelper.GetMimeType).ToHashSet(); return Json(mimeTypes); } diff --git a/starsky/starsky/Controllers/AppSettingsFeaturesController.cs b/starsky/starsky/Controllers/AppSettingsFeaturesController.cs index 20c1df28fa..1ca956296d 100644 --- a/starsky/starsky/Controllers/AppSettingsFeaturesController.cs +++ b/starsky/starsky/Controllers/AppSettingsFeaturesController.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Mvc; using starsky.feature.trash.Interfaces; using starsky.foundation.platform.Models; -using starskycore.ViewModels; +using starsky.project.web.ViewModels; namespace starsky.Controllers; diff --git a/starsky/starsky/Controllers/DiskController.cs b/starsky/starsky/Controllers/DiskController.cs index 855b1b3fa0..d7b12c3887 100644 --- a/starsky/starsky/Controllers/DiskController.cs +++ b/starsky/starsky/Controllers/DiskController.cs @@ -13,7 +13,7 @@ using starsky.foundation.realtime.Interfaces; using starsky.foundation.storage.Interfaces; using starsky.foundation.storage.Storage; -using starskycore.ViewModels; +using starsky.project.web.ViewModels; namespace starsky.Controllers { @@ -62,11 +62,9 @@ public async Task Mkdir(string f) foreach ( var subPath in inputFilePaths.Select(PathHelper.RemoveLatestSlash) ) { - var toAddStatus = new SyncViewModel { - FilePath = subPath, - Status = FileIndexItem.ExifStatus.Ok + FilePath = subPath, Status = FileIndexItem.ExifStatus.Ok }; if ( _iStorage.ExistFolder(subPath) ) @@ -76,10 +74,7 @@ public async Task Mkdir(string f) continue; } - await _query.AddItemAsync(new FileIndexItem(subPath) - { - IsDirectory = true - }); + await _query.AddItemAsync(new FileIndexItem(subPath) { IsDirectory = true }); // add to fs _iStorage.CreateDirectory(subPath); @@ -102,12 +97,12 @@ await _query.AddItemAsync(new FileIndexItem(subPath) /// SyncViewModel /// optional debug name /// Completed send of Socket SendToAllAsync - private async Task SyncMessageToSocket(IEnumerable syncResultsList, ApiNotificationType type = ApiNotificationType.Unknown) + private async Task SyncMessageToSocket(IEnumerable syncResultsList, + ApiNotificationType type = ApiNotificationType.Unknown) { var list = syncResultsList.Select(t => new FileIndexItem(t.FilePath) { - Status = t.Status, - IsDirectory = true + Status = t.Status, IsDirectory = true }).ToList(); var webSocketResponse = new ApiNotificationResponseModel< @@ -132,7 +127,8 @@ private async Task SyncMessageToSocket(IEnumerable syncResultsLis [ProducesResponseType(typeof(List), 404)] [HttpPost("/api/disk/rename")] [Produces("application/json")] - public async Task Rename(string f, string to, bool collections = true, bool currentStatus = true) + public async Task Rename(string f, string to, bool collections = true, + bool currentStatus = true) { if ( string.IsNullOrEmpty(f) ) { @@ -146,14 +142,16 @@ public async Task Rename(string f, string to, bool collections = return NotFound(rename); var webSocketResponse = - new ApiNotificationResponseModel>(rename, ApiNotificationType.Rename); + new ApiNotificationResponseModel>(rename, + ApiNotificationType.Rename); await _notificationQuery.AddNotification(webSocketResponse); await _connectionsService.SendToAllAsync(webSocketResponse, CancellationToken.None); - return Json(currentStatus ? rename.Where(p => p.Status - != FileIndexItem.ExifStatus.NotFoundSourceMissing).ToList() : rename); + return Json(currentStatus + ? rename.Where(p => p.Status + != FileIndexItem.ExifStatus.NotFoundSourceMissing).ToList() + : rename); } - } } diff --git a/starsky/starsky/Controllers/DownloadPhotoController.cs b/starsky/starsky/Controllers/DownloadPhotoController.cs index ee409f1f02..509b7f9924 100644 --- a/starsky/starsky/Controllers/DownloadPhotoController.cs +++ b/starsky/starsky/Controllers/DownloadPhotoController.cs @@ -10,7 +10,7 @@ using starsky.foundation.storage.Storage; using starsky.foundation.thumbnailgeneration.Interfaces; using starsky.Helpers; -using starskycore.Helpers; +using starsky.project.web.Helpers; namespace starsky.Controllers { diff --git a/starsky/starsky/Controllers/ExportController.cs b/starsky/starsky/Controllers/ExportController.cs index 0dd68b2d14..22d2d98d64 100644 --- a/starsky/starsky/Controllers/ExportController.cs +++ b/starsky/starsky/Controllers/ExportController.cs @@ -10,7 +10,7 @@ using starsky.foundation.storage.Interfaces; using starsky.foundation.storage.Storage; using starsky.foundation.worker.Interfaces; -using starskycore.Helpers; +using starsky.project.web.Helpers; namespace starsky.Controllers { diff --git a/starsky/starsky/Controllers/HealthController.cs b/starsky/starsky/Controllers/HealthController.cs index ceb03fb6e6..98f630648a 100644 --- a/starsky/starsky/Controllers/HealthController.cs +++ b/starsky/starsky/Controllers/HealthController.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.Diagnostics.HealthChecks; using starsky.foundation.platform.Extensions; using starsky.foundation.platform.VersionHelpers; -using starskycore.ViewModels; +using starsky.project.web.ViewModels; [assembly: InternalsVisibleTo("starskytest")] @@ -60,9 +60,9 @@ internal async Task CheckHealthAsyncWithTimeout(int timeoutTime = try { if ( _cache != null && - _cache.TryGetValue(healthControllerCacheKey, out var objectHealthStatus) && - objectHealthStatus is HealthReport healthStatus && - healthStatus.Status == HealthStatus.Healthy ) + _cache.TryGetValue(healthControllerCacheKey, out var objectHealthStatus) && + objectHealthStatus is HealthReport healthStatus && + healthStatus.Status == HealthStatus.Healthy ) { return healthStatus; } @@ -132,7 +132,7 @@ private static HealthView CreateHealthEntryLog(HealthReport result) Name = key, IsHealthy = value.Status == HealthStatus.Healthy, Description = value.Description + value.Exception?.Message + - value.Exception?.StackTrace + value.Exception?.StackTrace } ); } diff --git a/starsky/starsky/Controllers/IndexController.cs b/starsky/starsky/Controllers/IndexController.cs index b240aee0db..0536625a81 100644 --- a/starsky/starsky/Controllers/IndexController.cs +++ b/starsky/starsky/Controllers/IndexController.cs @@ -6,7 +6,7 @@ using starsky.foundation.database.Models; using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Models; -using starskycore.ViewModels; +using starsky.project.web.ViewModels; namespace starsky.Controllers { diff --git a/starsky/starsky/Controllers/MetricsDebugController.cs b/starsky/starsky/Controllers/MetricsDebugController.cs index 9108760e2a..8318079bc3 100644 --- a/starsky/starsky/Controllers/MetricsDebugController.cs +++ b/starsky/starsky/Controllers/MetricsDebugController.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using starsky.foundation.worker.CpuEventListener.Interfaces; -using starskycore.ViewModels; +using starsky.project.web.ViewModels; namespace starsky.Controllers; @@ -18,9 +18,6 @@ public MetricsDebugController(ICpuUsageListener cpuUsageListener) public IActionResult Index() { - return Json(new MetricsDebugViewModel - { - CpuUsageMean = _cpuUsageListener.CpuUsageMean, - }); + return Json(new MetricsDebugViewModel { CpuUsageMean = _cpuUsageListener.CpuUsageMean, }); } } diff --git a/starsky/starsky/Controllers/ThumbnailController.cs b/starsky/starsky/Controllers/ThumbnailController.cs index 390b3740d0..8c97aaf858 100644 --- a/starsky/starsky/Controllers/ThumbnailController.cs +++ b/starsky/starsky/Controllers/ThumbnailController.cs @@ -14,7 +14,7 @@ using starsky.foundation.storage.Interfaces; using starsky.foundation.storage.Models; using starsky.foundation.storage.Storage; -using starskycore.Helpers; +using starsky.project.web.Helpers; namespace starsky.Controllers { @@ -54,22 +54,29 @@ public IActionResult ThumbnailSmallOrTinyMeta(string f) // Restrict the fileHash to letters and digits only // I/O function calls should not be vulnerable to path injection attacks if ( !Regex.IsMatch(f, "^[a-zA-Z0-9_-]+$", - RegexOptions.None, TimeSpan.FromMilliseconds(200)) ) + RegexOptions.None, TimeSpan.FromMilliseconds(200)) ) { return BadRequest(); } if ( _thumbnailStorage.ExistFile(ThumbnailNameHelper.Combine(f, ThumbnailSize.Small)) ) { - var stream = _thumbnailStorage.ReadStream(ThumbnailNameHelper.Combine(f, ThumbnailSize.Small)); - Response.Headers.TryAdd("x-image-size", new StringValues(ThumbnailSize.Small.ToString())); + var stream = + _thumbnailStorage.ReadStream( + ThumbnailNameHelper.Combine(f, ThumbnailSize.Small)); + Response.Headers.TryAdd("x-image-size", + new StringValues(ThumbnailSize.Small.ToString())); return File(stream, "image/jpeg"); } - if ( _thumbnailStorage.ExistFile(ThumbnailNameHelper.Combine(f, ThumbnailSize.TinyMeta)) ) + if ( _thumbnailStorage.ExistFile( + ThumbnailNameHelper.Combine(f, ThumbnailSize.TinyMeta)) ) { - var stream = _thumbnailStorage.ReadStream(ThumbnailNameHelper.Combine(f, ThumbnailSize.TinyMeta)); - Response.Headers.TryAdd("x-image-size", new StringValues(ThumbnailSize.TinyMeta.ToString())); + var stream = + _thumbnailStorage.ReadStream( + ThumbnailNameHelper.Combine(f, ThumbnailSize.TinyMeta)); + Response.Headers.TryAdd("x-image-size", + new StringValues(ThumbnailSize.TinyMeta.ToString())); return File(stream, "image/jpeg"); } @@ -79,8 +86,10 @@ public IActionResult ThumbnailSmallOrTinyMeta(string f) return NotFound("hash not found"); } - var streamDefaultThumbnail = _thumbnailStorage.ReadStream(ThumbnailNameHelper.Combine(f, ThumbnailSize.Large)); - Response.Headers.TryAdd("x-image-size", new StringValues(ThumbnailSize.Large.ToString())); + var streamDefaultThumbnail = + _thumbnailStorage.ReadStream(ThumbnailNameHelper.Combine(f, ThumbnailSize.Large)); + Response.Headers.TryAdd("x-image-size", + new StringValues(ThumbnailSize.Large.ToString())); return File(streamDefaultThumbnail, "image/jpeg"); } @@ -102,7 +111,6 @@ public IActionResult ThumbnailSmallOrTinyMeta(string f) [ProducesResponseType( 400)] // string (f) input not allowed to avoid path injection attacks [ProducesResponseType(404)] // not found - public async Task ListSizesByHash(string f) { // For serving jpeg files @@ -111,17 +119,25 @@ public async Task ListSizesByHash(string f) // Restrict the fileHash to letters and digits only // I/O function calls should not be vulnerable to path injection attacks if ( !Regex.IsMatch(f, "^[a-zA-Z0-9_-]+$", - RegexOptions.None, TimeSpan.FromMilliseconds(100)) ) + RegexOptions.None, TimeSpan.FromMilliseconds(100)) ) { return BadRequest(); } var data = new ThumbnailSizesExistStatusModel { - TinyMeta = _thumbnailStorage.ExistFile(ThumbnailNameHelper.Combine(f, ThumbnailSize.TinyMeta)), - Small = _thumbnailStorage.ExistFile(ThumbnailNameHelper.Combine(f, ThumbnailSize.Small)), - Large = _thumbnailStorage.ExistFile(ThumbnailNameHelper.Combine(f, ThumbnailSize.Large)), - ExtraLarge = _thumbnailStorage.ExistFile(ThumbnailNameHelper.Combine(f, ThumbnailSize.ExtraLarge)) + TinyMeta = + _thumbnailStorage.ExistFile( + ThumbnailNameHelper.Combine(f, ThumbnailSize.TinyMeta)), + Small = + _thumbnailStorage.ExistFile( + ThumbnailNameHelper.Combine(f, ThumbnailSize.Small)), + Large = + _thumbnailStorage.ExistFile( + ThumbnailNameHelper.Combine(f, ThumbnailSize.Large)), + ExtraLarge = + _thumbnailStorage.ExistFile( + ThumbnailNameHelper.Combine(f, ThumbnailSize.ExtraLarge)) }; // Success has all items (except tinyMeta) @@ -138,11 +154,11 @@ public async Task ListSizesByHash(string f) return Json(data); case false when !string.IsNullOrEmpty(sourcePath): Response.StatusCode = 210; // A conflict, that the thumb is not generated yet - return Json("Thumbnail is not supported; for example you try to view a raw or video file"); + return Json( + "Thumbnail is not supported; for example you try to view a raw or video file"); default: return NotFound("not in index"); } - } private IActionResult ReturnThumbnailResult(string f, bool json, ThumbnailSize size) @@ -161,10 +177,11 @@ private IActionResult ReturnThumbnailResult(string f, bool json, ThumbnailSize s if ( json ) return Json("OK"); stream = _thumbnailStorage.ReadStream( - ThumbnailNameHelper.Combine(f, size)); + ThumbnailNameHelper.Combine(f, size)); // thumbs are always in jpeg - Response.Headers.Append("x-filename", new StringValues(FilenamesHelper.GetFileName(f + ".jpg"))); + Response.Headers.Append("x-filename", + new StringValues(FilenamesHelper.GetFileName(f + ".jpg"))); return File(stream, "image/jpeg"); } @@ -218,7 +235,7 @@ public async Task Thumbnail( // Restrict the fileHash to letters and digits only // I/O function calls should not be vulnerable to path injection attacks if ( !Regex.IsMatch(f, "^[a-zA-Z0-9_-]+$", - RegexOptions.None, TimeSpan.FromMilliseconds(100)) ) + RegexOptions.None, TimeSpan.FromMilliseconds(100)) ) { return BadRequest(); } @@ -249,7 +266,8 @@ public async Task Thumbnail( // remove from cache _query.ResetItemByHash(f); - if ( string.IsNullOrEmpty(filePath) || await _query.GetObjectByFilePathAsync(filePath) == null ) + if ( string.IsNullOrEmpty(filePath) || + await _query.GetObjectByFilePathAsync(filePath) == null ) { SetExpiresResponseHeadersToZero(); return NotFound("not in index"); @@ -261,7 +279,7 @@ public async Task Thumbnail( if ( !_iStorage.ExistFile(sourcePath) ) { return NotFound("There is no thumbnail image " + f + " and no source image " + - sourcePath); + sourcePath); } if ( !isSingleItem ) @@ -316,7 +334,7 @@ public async Task ByZoomFactorAsync( // Restrict the fileHash to letters and digits only // I/O function calls should not be vulnerable to path injection attacks if ( !Regex.IsMatch(f, "^[a-zA-Z0-9_-]+$", - RegexOptions.None, TimeSpan.FromMilliseconds(100)) ) + RegexOptions.None, TimeSpan.FromMilliseconds(100)) ) { return BadRequest(); } @@ -329,6 +347,7 @@ public async Task ByZoomFactorAsync( { return NotFound("not in index"); } + sourcePath = filePath; } @@ -351,7 +370,8 @@ public async Task ByZoomFactorAsync( public void SetExpiresResponseHeadersToZero() { Request.HttpContext.Response.Headers.Remove("Cache-Control"); - Request.HttpContext.Response.Headers.Append("Cache-Control", "no-cache, no-store, must-revalidate"); + Request.HttpContext.Response.Headers.Append("Cache-Control", + "no-cache, no-store, must-revalidate"); Request.HttpContext.Response.Headers.Remove("Pragma"); Request.HttpContext.Response.Headers.Append("Pragma", "no-cache"); diff --git a/starsky/starsky/Program.cs b/starsky/starsky/Program.cs index 8ef97230e0..9714caa7db 100644 --- a/starsky/starsky/Program.cs +++ b/starsky/starsky/Program.cs @@ -8,13 +8,14 @@ using Microsoft.Extensions.Hosting; using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Models; +using starsky.project.web.Helpers; namespace starsky { public static class Program { [SuppressMessage("Usage", "S6603: The collection-specific TrueForAll " + - "method should be used instead of the All extension")] + "method should be used instead of the All extension")] public static async Task Main(string[] args) { var appSettingsPath = Path.Join( @@ -49,6 +50,7 @@ internal static async Task RunAsync(WebApplication webApplication, { return false; } + return true; } @@ -65,7 +67,7 @@ private static WebApplicationBuilder CreateWebHostBuilder(string[] args) builder.WebHost.ConfigureKestrel(k => { k.Limits.MaxRequestLineSize = 65536; //64Kb - // AddServerHeader removes the header: Server: Kestrel + // AddServerHeader removes the header: Server: Kestrel k.AddServerHeader = false; }); diff --git a/starsky/starsky/starsky.csproj b/starsky/starsky/starsky.csproj index 2ae3c67693..71aed3f202 100644 --- a/starsky/starsky/starsky.csproj +++ b/starsky/starsky/starsky.csproj @@ -166,7 +166,7 @@ - + diff --git a/starsky/starskybusinesslogic/readme.md b/starsky/starskybusinesslogic/readme.md index fd6579bac4..d14763ac46 100644 --- a/starsky/starskybusinesslogic/readme.md +++ b/starsky/starskybusinesslogic/readme.md @@ -1,21 +1,29 @@ # Business Logic + ## List of [Starsky](../../readme.md) Projects - * [By App documentation](../../starsky/readme.md) _database photo index & import index project_ + +* [By App documentation](../../starsky/readme.md) _database photo index & import index project_ * [starsky](../../starsky/starsky/readme.md) _web api application / interface_ - * [clientapp](../../starsky/starsky/clientapp/readme.md) _react front-end application_ - * [starskyImporterCli](../../starsky/starskyimportercli/readme.md) _import command line interface_ + * [clientapp](../../starsky/starsky/clientapp/readme.md) _react front-end application_ + * [starskyImporterCli](../../starsky/starskyimportercli/readme.md) _import command line + interface_ * [starskyGeoCli](../../starsky/starskygeocli/readme.md) _gpx sync and reverse 'geo tagging'_ - * [starskyWebHtmlCli](../../starsky/starskywebhtmlcli/readme.md) _publish web images to a content package_ - * [starskyWebFtpCli](../../starsky/starskywebftpcli/readme.md) _copy a content package to a ftp service_ + * [starskyWebHtmlCli](../../starsky/starskywebhtmlcli/readme.md) _publish web images to a + content package_ + * [starskyWebFtpCli](../../starsky/starskywebftpcli/readme.md) _copy a content package to a ftp + service_ * [starskyAdminCli](../../starsky/starskyadmincli/readme.md) _manage user accounts_ - * [starskySynchronizeCli](../../starsky/starskysynchronizecli/readme.md) _check if disk changes are updated in the database_ - * [starskyThumbnailCli](../../starsky/starskythumbnailcli/readme.md) _speed web performance by generating smaller images_ - * __[Starsky Business Logic](../../starsky/starskybusinesslogic/readme.md) business logic libraries (.NET)__ + * [starskySynchronizeCli](../../starsky/starskysynchronizecli/readme.md) _check if disk changes + are updated in the database_ + * [starskyThumbnailCli](../../starsky/starskythumbnailcli/readme.md) _speed web performance by + generating smaller images_ + * __[Starsky Business Logic](../../starsky/starskybusinesslogic/readme.md) business logic + libraries (.NET)__ * [starskyTest](../../starsky/starskytest/readme.md) _mstest unit tests (for .NET)_ - * [starsky-tools](../../starsky-tools/readme.md) _nodejs tools to add-on tasks_ - * [Starsky Desktop](../../starskydesktop/readme.md) _Desktop Application_ +* [starsky-tools](../../starsky-tools/readme.md) _nodejs tools to add-on tasks_ +* [Starsky Desktop](../../starskydesktop/readme.md) _Desktop Application_ * [Download Desktop App](https://docs.qdraw.nl/download/) _Windows and Mac OS version_ - * [Changelog](../../history.md) _Release notes and history_ +* [Changelog](../../history.md) _Release notes and history_ ## Starsky Business Logic docs @@ -23,40 +31,39 @@ This is an overview of business logic ## Feature compare table -| Feature | Present | -|-------------------------------------------------------------------|---------| -| Anywhere secure access | βœ… | -| Native iOS and Android mobile apps | ❌ | -| Mac OS and Windows Desktop app | βœ… | -| Access controls with permissions and roles | ✴️ | -| User generation by Command Line | βœ… | -| Customized branded login page | ❌ | -| Out-of-the-box access from the web (when hosted) | βœ… | -| SaaS solution | ❌ | -| Multi tenant support | ❌ | -| Bulk metadata upload via CSV | ❌ | -| Bulk metadata edit via web interface | βœ… | -| Review, approve and publish uploads | ❌ | -| Batch or single file download | βœ… | -| Download permissions based on role | ❌ | -| Request access to file form | ❌ | -| Supports photos jpg, png, gif, tiff | βœ… | -| Supports video mp4 (H.264) | βœ… | -| Supports audio | ❌ | -| Supports IPTC, EXIF and XMP metadata | βœ… | -| All major browsers supported (Chrome, Safari, Mozilla) | βœ… | -| Internet Explorer support | ❌ | -| In-line editing in fields | βœ… | -| Localized platform English and Dutch | βœ… | -| Host the server version yourself using docker | βœ… | -| Host the server version yourself on a Windows/Mac/Linux | βœ… | - +| Feature | Present | +|---------------------------------------------------------|---------| +| Anywhere secure access | βœ… | +| Native iOS and Android mobile apps | ❌ | +| Mac OS and Windows Desktop app | βœ… | +| Access controls with permissions and roles | ✴️ | +| User generation by Command Line | βœ… | +| Customized branded login page | ❌ | +| Out-of-the-box access from the web (when hosted) | βœ… | +| SaaS solution | ❌ | +| Multi tenant support | ❌ | +| Bulk metadata upload via CSV | ❌ | +| Bulk metadata edit via web interface | βœ… | +| Review, approve and publish uploads | ❌ | +| Batch or single file download | βœ… | +| Download permissions based on role | ❌ | +| Request access to file form | ❌ | +| Supports photos jpg, png, gif, tiff | βœ… | +| Supports video mp4 (H.264) | βœ… | +| Supports audio | ❌ | +| Supports IPTC, EXIF and XMP metadata | βœ… | +| All major browsers supported (Chrome, Safari, Mozilla) | βœ… | +| Internet Explorer support | ❌ | +| In-line editing in fields | βœ… | +| Localized platform English and Dutch | βœ… | +| Host the server version yourself using docker | βœ… | +| Host the server version yourself on a Windows/Mac/Linux | βœ… | | Icon | Meaning of icon | |------|-----------------------| -| βœ… | fully implemented | -| ✴️ | is partly implemented | -| ❌ | not implemented | +| βœ… | fully implemented | +| ✴️ | is partly implemented | +| ❌ | not implemented | ## Project structure @@ -88,6 +95,18 @@ This is an overview of business logic | | Copy webhtmlpublish-ed items to an ftp server | └── starsky.feature.webhtmlpublish | Generate html content with photos. +| └── starsky.feature.realtime +| Real-time features +| └── starsky.feature.syncbackground +| Background synchronization features +| └── starsky.feature.search +| Search features +| └── starsky.feature.thumbnail +| Thumbnail-related features +| └── starsky.feature.settings +| Settings features +| └── starsky.feature.demo +| Demo features └── Foundation | | Modules in the Foundation layer are conceptually abstract and do not contain presentation in the form of renderings or views | └── starsky.foundation.accountmanagement @@ -112,10 +131,18 @@ This is an overview of business logic | | Generate Thumbnails | └── starsky.foundation.writemeta | Write though Exiftool +| └── starsky.foundation.worker +| Worker-related functionalities +| └── starsky.foundation.settings +| Settings-related functionalities +| └── starsky.foundation.native +| Native OS-related functionalities +| └── starsky.foundation.thumbnailmeta +| Thumbnail meta-related functionalities └── Project | This means the actual cohesive website or channel output from the implementation, such as the page types, layout and graphical design - └── starskycore - | To be depricated and replaced with feature and foundation services + └── starsky.project.web + | Services and helpers needed for the web application, but not for other applications └── starsky WebAPI presentation application (see ClientApp for more details about the UI) ``` diff --git a/starsky/starskycore/Attributes/ExcludeFromCoverageAttribute.cs b/starsky/starskycore/Attributes/ExcludeFromCoverageAttribute.cs deleted file mode 100644 index 260e1bcf43..0000000000 --- a/starsky/starskycore/Attributes/ExcludeFromCoverageAttribute.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System; - -namespace starskycore.Attributes -{ - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor)] - public sealed class ExcludeFromCoverageAttribute : Attribute { } -} diff --git a/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs b/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs index 925ae5b4d4..e305c2a9e3 100644 --- a/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs +++ b/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs @@ -4,7 +4,7 @@ using starsky.Controllers; using starsky.foundation.database.Models; using starsky.foundation.platform.Models; -using starskycore.ViewModels; +using starsky.project.web.ViewModels; using starskytest.FakeMocks; namespace starskytest.Controllers; diff --git a/starsky/starskytest/Controllers/DiskControllerTest.cs b/starsky/starskytest/Controllers/DiskControllerTest.cs index 712f7b4b37..72e2471710 100644 --- a/starsky/starskytest/Controllers/DiskControllerTest.cs +++ b/starsky/starskytest/Controllers/DiskControllerTest.cs @@ -22,10 +22,9 @@ using starsky.foundation.worker.Interfaces; using starsky.foundation.worker.Services; using starsky.foundation.writemeta.Interfaces; -using starskycore.ViewModels; +using starsky.project.web.ViewModels; using starskytest.FakeCreateAn; using starskytest.FakeMocks; -using starskytest.Models; namespace starskytest.Controllers { @@ -62,9 +61,9 @@ public DiskControllerTest() _createAnImage = new CreateAnImage(); var dict = new Dictionary { - {"App:StorageFolder", _createAnImage.BasePath}, - {"App:ThumbnailTempFolder", _createAnImage.BasePath}, - {"App:Verbose", "true"} + { "App:StorageFolder", _createAnImage.BasePath }, + { "App:ThumbnailTempFolder", _createAnImage.BasePath }, + { "App:Verbose", "true" } }; // Start using dependency injection var builder = new ConfigurationBuilder(); @@ -83,24 +82,24 @@ public DiskControllerTest() var serviceProvider = services.BuildServiceProvider(); // get the service var appSettings = serviceProvider.GetRequiredService(); - + var scopeFactory = serviceProvider.GetRequiredService(); - _query = new Query(context, appSettings, scopeFactory, new FakeIWebLogger(), memoryCache); + _query = new Query(context, appSettings, scopeFactory, new FakeIWebLogger(), + memoryCache); } private async Task InsertSearchData() { - _iStorage = new FakeIStorage(new List { "/" }, + _iStorage = new FakeIStorage(new List { "/" }, new List { _createAnImage.DbPath }); - var fileHashCode = (await new FileHash(_iStorage).GetHashCodeAsync(_createAnImage.DbPath)).Key; - + var fileHashCode = + ( await new FileHash(_iStorage).GetHashCodeAsync(_createAnImage.DbPath) ).Key; + if ( string.IsNullOrEmpty(await _query.GetSubPathByHashAsync(fileHashCode)) ) { await _query.AddItemAsync(new FileIndexItem { - FileName = "/", - ParentDirectory = "/", - IsDirectory = true + FileName = "/", ParentDirectory = "/", IsDirectory = true }); await _query.AddItemAsync(new FileIndexItem @@ -115,103 +114,94 @@ await _query.AddItemAsync(new FileIndexItem _query.GetObjectByFilePath(_createAnImage.DbPath); } - + [TestMethod] public async Task SyncControllerTest_Rename_NotFoundInIndex() { - - var context = new ControllerContext - { - HttpContext = new DefaultHttpContext() - }; + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; var fakeStorage = new FakeIStorage(); var storageSelector = new FakeSelectorStorage(fakeStorage); - - var controller = new DiskController(_query, storageSelector, + + var controller = new DiskController(_query, storageSelector, new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()); controller.ControllerContext = context; - var result = await controller.Rename("/notfound-image.jpg", "/test.jpg") as NotFoundObjectResult; - - Assert.AreEqual(404,result?.StatusCode); + var result = + await controller.Rename("/notfound-image.jpg", "/test.jpg") as NotFoundObjectResult; + + Assert.AreEqual(404, result?.StatusCode); } - + [TestMethod] public async Task SyncControllerTest_BadRequest() { - var context = new ControllerContext - { - HttpContext = new DefaultHttpContext() - }; + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; var fakeStorage = new FakeIStorage(); var storageSelector = new FakeSelectorStorage(fakeStorage); - - var controller = new DiskController(_query, storageSelector, + + var controller = new DiskController(_query, storageSelector, new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()); controller.ControllerContext = context; - var result = await controller.Rename(string.Empty, "/test.jpg") as BadRequestObjectResult; - - Assert.AreEqual(400,result?.StatusCode); + var result = + await controller.Rename(string.Empty, "/test.jpg") as BadRequestObjectResult; + + Assert.AreEqual(400, result?.StatusCode); } - + [TestMethod] public async Task SyncControllerTest_Rename_Good() { await InsertSearchData(); - var context = new ControllerContext - { - HttpContext = new DefaultHttpContext() - }; - - var fakeStorage = new FakeIStorage(new List { "/" }, + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; + + var fakeStorage = new FakeIStorage(new List { "/" }, new List { _createAnImage.DbPath }); var storageSelector = new FakeSelectorStorage(fakeStorage); - + var controller = - new DiskController( _query, storageSelector, + new DiskController(_query, storageSelector, new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()) { ControllerContext = context }; - + var result = await controller.Rename(_createAnImage.DbPath, "/test.jpg") as JsonResult; var list = result?.Value as List; - Assert.AreEqual(FileIndexItem.ExifStatus.Ok,list?.FirstOrDefault()?.Status); + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, list?.FirstOrDefault()?.Status); - await _query.RemoveItemAsync((await _query.GetObjectByFilePathAsync("/test.jpg"))!); + await _query.RemoveItemAsync(( await _query.GetObjectByFilePathAsync("/test.jpg") )!); } - + [TestMethod] public async Task SyncControllerTest_Rename_WithCurrentStatusDisabled() { await InsertSearchData(); - var context = new ControllerContext - { - HttpContext = new DefaultHttpContext() - }; - - var fakeStorage = new FakeIStorage(new List { "/" }, + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; + + var fakeStorage = new FakeIStorage(new List { "/" }, new List { _createAnImage.DbPath }); var storageSelector = new FakeSelectorStorage(fakeStorage); - + var controller = - new DiskController(_query, storageSelector, + new DiskController(_query, storageSelector, new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()) { ControllerContext = context }; - - var result = await controller.Rename(_createAnImage.DbPath, "/test.jpg", true, false) as JsonResult; + + var result = + await controller.Rename(_createAnImage.DbPath, "/test.jpg", true, false) as + JsonResult; var list = result?.Value as List; - Assert.AreEqual(FileIndexItem.ExifStatus.Ok,list?[0].Status); - Assert.AreEqual(FileIndexItem.ExifStatus.NotFoundSourceMissing,list?[1].Status); + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, list?[0].Status); + Assert.AreEqual(FileIndexItem.ExifStatus.NotFoundSourceMissing, list?[1].Status); - await _query.RemoveItemAsync((await _query.GetObjectByFilePathAsync("/test.jpg"))!); + await _query.RemoveItemAsync(( await _query.GetObjectByFilePathAsync("/test.jpg") )!); } [TestMethod] @@ -219,134 +209,114 @@ public async Task SyncControllerTest_Rename_Good_SocketUpdate() { await InsertSearchData(); - var context = new ControllerContext - { - HttpContext = new DefaultHttpContext() - }; + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; var socket = new FakeIWebSocketConnectionsService(); - var fakeStorage = new FakeIStorage(new List { "/" }, + var fakeStorage = new FakeIStorage(new List { "/" }, new List { _createAnImage.DbPath }); var storageSelector = new FakeSelectorStorage(fakeStorage); - + var controller = - new DiskController(_query, storageSelector, - socket, new FakeINotificationQuery()) - { - ControllerContext = context - }; - + new DiskController(_query, storageSelector, + socket, new FakeINotificationQuery()) { ControllerContext = context }; + await controller.Rename(_createAnImage.DbPath, "/test.jpg"); - - Assert.AreEqual(1,socket.FakeSendToAllAsync.Count(p => !p.Contains("[system]"))); + + Assert.AreEqual(1, socket.FakeSendToAllAsync.Count(p => !p.Contains("[system]"))); Assert.IsTrue(socket.FakeSendToAllAsync[0].Contains("/test.jpg")); - - await _query.RemoveItemAsync((await _query.GetObjectByFilePathAsync("/test.jpg"))!); + + await _query.RemoveItemAsync(( await _query.GetObjectByFilePathAsync("/test.jpg") )!); } [TestMethod] public async Task SyncControllerTest_Mkdir_Good() { await InsertSearchData(); - var context = new ControllerContext - { - HttpContext = new DefaultHttpContext() - }; - - var fakeStorage = new FakeIStorage(new List { "/" }, + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; + + var fakeStorage = new FakeIStorage(new List { "/" }, new List { _createAnImage.DbPath }); var storageSelector = new FakeSelectorStorage(fakeStorage); var controller = - new DiskController( _query, storageSelector, + new DiskController(_query, storageSelector, new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()) { ControllerContext = context }; - + var result = await controller.Mkdir("/test_dir") as JsonResult; var list = result?.Value as List; - Assert.AreEqual(FileIndexItem.ExifStatus.Ok,list?.FirstOrDefault()?.Status); + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, list?.FirstOrDefault()?.Status); } - + [TestMethod] public async Task SyncControllerTest_Mkdir_Good_SocketUpdate() { await InsertSearchData(); - var context = new ControllerContext - { - HttpContext = new DefaultHttpContext() - }; + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; var socket = new FakeIWebSocketConnectionsService(); - var fakeStorage = new FakeIStorage(new List { "/" }, + var fakeStorage = new FakeIStorage(new List { "/" }, new List { _createAnImage.DbPath }); var storageSelector = new FakeSelectorStorage(fakeStorage); var controller = - new DiskController(_query, storageSelector, - socket, new FakeINotificationQuery()) - { - ControllerContext = context - }; - + new DiskController(_query, storageSelector, + socket, new FakeINotificationQuery()) { ControllerContext = context }; + await controller.Mkdir("/test_dir"); - + var value = socket.FakeSendToAllAsync.Find(p => !p.StartsWith("[system]")); - + Assert.IsNotNull(value); Assert.IsTrue(value.Contains("/test_dir")); } - + [TestMethod] public async Task SyncControllerTest_Mkdir_Exist() { await InsertSearchData(); - var context = new ControllerContext - { - HttpContext = new DefaultHttpContext() - }; - - var fakeStorage = new FakeIStorage(new List { "/" ,"/test_dir" }, + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; + + var fakeStorage = new FakeIStorage(new List { "/", "/test_dir" }, new List { _createAnImage.DbPath }); var storageSelector = new FakeSelectorStorage(fakeStorage); var controller = - new DiskController( _query, storageSelector, + new DiskController(_query, storageSelector, new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()) { ControllerContext = context }; - + var result = await controller.Mkdir("/test_dir") as JsonResult; var list = result?.Value as List; - Assert.AreEqual(FileIndexItem.ExifStatus.OperationNotSupported,list?.FirstOrDefault()?.Status); + Assert.AreEqual(FileIndexItem.ExifStatus.OperationNotSupported, + list?.FirstOrDefault()?.Status); } - - + + [TestMethod] public async Task Mkdir_BadRequest() { - var context = new ControllerContext - { - HttpContext = new DefaultHttpContext() - }; - - var fakeStorage = new FakeIStorage(new List { "/" ,"/test_dir" }, + var context = new ControllerContext { HttpContext = new DefaultHttpContext() }; + + var fakeStorage = new FakeIStorage(new List { "/", "/test_dir" }, new List { _createAnImage.DbPath }); var storageSelector = new FakeSelectorStorage(fakeStorage); var controller = - new DiskController( _query, storageSelector, + new DiskController(_query, storageSelector, new FakeIWebSocketConnectionsService(), new FakeINotificationQuery()) { ControllerContext = context }; await controller.Mkdir(string.Empty); - - Assert.AreEqual(400,context.HttpContext.Response.StatusCode); + + Assert.AreEqual(400, context.HttpContext.Response.StatusCode); } } } diff --git a/starsky/starskytest/Controllers/ExportControllerTest.cs b/starsky/starskytest/Controllers/ExportControllerTest.cs index 11dc80b701..056e3cdba0 100644 --- a/starsky/starskytest/Controllers/ExportControllerTest.cs +++ b/starsky/starskytest/Controllers/ExportControllerTest.cs @@ -32,7 +32,6 @@ using starsky.foundation.writemeta.Interfaces; using starskytest.FakeCreateAn; using starskytest.FakeMocks; -using starskytest.Models; namespace starskytest.Controllers { diff --git a/starsky/starskytest/Controllers/GeoControllerTest.cs b/starsky/starskytest/Controllers/GeoControllerTest.cs index 8d15bea800..00b35f3421 100644 --- a/starsky/starskytest/Controllers/GeoControllerTest.cs +++ b/starsky/starskytest/Controllers/GeoControllerTest.cs @@ -23,7 +23,6 @@ using starsky.foundation.writemeta.Interfaces; using starskytest.FakeCreateAn; using starskytest.FakeMocks; -using starskytest.Models; namespace starskytest.Controllers { diff --git a/starsky/starskytest/Controllers/HealthControllerTest.cs b/starsky/starskytest/Controllers/HealthControllerTest.cs index 49c6967e1b..055103a9d6 100644 --- a/starsky/starskytest/Controllers/HealthControllerTest.cs +++ b/starsky/starskytest/Controllers/HealthControllerTest.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.Controllers; -using starskycore.ViewModels; +using starsky.project.web.ViewModels; using starskytest.FakeMocks; namespace starskytest.Controllers diff --git a/starsky/starskytest/Controllers/ImportControllerTest.cs b/starsky/starskytest/Controllers/ImportControllerTest.cs index 35a2b7ade4..a1f3a14eca 100644 --- a/starsky/starskytest/Controllers/ImportControllerTest.cs +++ b/starsky/starskytest/Controllers/ImportControllerTest.cs @@ -29,7 +29,6 @@ using starsky.foundation.writemeta.Interfaces; using starskytest.FakeCreateAn; using starskytest.FakeMocks; -using starskytest.Models; namespace starskytest.Controllers { diff --git a/starsky/starskytest/Controllers/IndexControllerTest.cs b/starsky/starskytest/Controllers/IndexControllerTest.cs index d0e3b66278..4c9e568afe 100644 --- a/starsky/starskytest/Controllers/IndexControllerTest.cs +++ b/starsky/starskytest/Controllers/IndexControllerTest.cs @@ -14,7 +14,7 @@ using starsky.foundation.database.Query; using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Models; -using starskycore.ViewModels; +using starsky.project.web.ViewModels; using starskytest.FakeMocks; namespace starskytest.Controllers @@ -30,7 +30,7 @@ public IndexControllerTest() .AddMemoryCache() .BuildServiceProvider(); var memoryCache = provider.GetService(); - + var builderDb = new DbContextOptionsBuilder(); builderDb.UseInMemoryDatabase("test"); var options = builderDb.Options; @@ -41,7 +41,7 @@ public IndexControllerTest() private async Task InsertSearchData() { - if (string.IsNullOrEmpty(await _query.GetSubPathByHashAsync("home0012304590"))) + if ( string.IsNullOrEmpty(await _query.GetSubPathByHashAsync("home0012304590")) ) { await _query.AddItemAsync(new FileIndexItem { @@ -50,13 +50,11 @@ await _query.AddItemAsync(new FileIndexItem FileHash = "home0012304590", ColorClass = ColorClassParser.Color.Winner // 1 }); - + // There must be a parent folder await _query.AddItemAsync(new FileIndexItem { - FileName = "homecontrollertest", - ParentDirectory = "", - IsDirectory = true + FileName = "homecontrollertest", ParentDirectory = "", IsDirectory = true }); } } @@ -68,24 +66,24 @@ public async Task HomeControllerIndexDetailViewTest() var controller = new IndexController(_query, new AppSettings()); controller.ControllerContext.HttpContext = new DefaultHttpContext(); var actionResult = controller.Index("/homecontrollertest/hi.jpg") as JsonResult; - Assert.AreNotEqual(null,actionResult); + Assert.AreNotEqual(null, actionResult); var jsonCollection = actionResult?.Value as DetailView; - Assert.AreEqual("home0012304590",jsonCollection?.FileIndexItem?.FileHash); + Assert.AreEqual("home0012304590", jsonCollection?.FileIndexItem?.FileHash); } [TestMethod] public async Task HomeControllerIndexIndexViewModelTest() { await InsertSearchData(); - var controller = new IndexController(_query,new AppSettings()); + var controller = new IndexController(_query, new AppSettings()); controller.ControllerContext.HttpContext = new DefaultHttpContext(); var actionResult = controller.Index("/homecontrollertest") as JsonResult; - Assert.AreNotEqual(null,actionResult); + Assert.AreNotEqual(null, actionResult); var jsonCollection = actionResult?.Value as ArchiveViewModel; - Assert.AreEqual("home0012304590",jsonCollection? + Assert.AreEqual("home0012304590", jsonCollection? .FileIndexItems.FirstOrDefault()?.FileHash); } - + [TestMethod] [SuppressMessage("ReSharper", "RedundantArgumentDefaultValue")] public void HomeControllerIndexIndexViewModel_SlashPage_Test() @@ -93,55 +91,55 @@ public void HomeControllerIndexIndexViewModel_SlashPage_Test() var fakeQuery = new FakeIQuery(new List { new FileIndexItem("/") { IsDirectory = true }, - new FileIndexItem("/test.jpg"){Tags = "test", FileHash = "test"} + new FileIndexItem("/test.jpg") { Tags = "test", FileHash = "test" } }); - - var controller = new IndexController(fakeQuery,new AppSettings()); + + var controller = new IndexController(fakeQuery, new AppSettings()); controller.ControllerContext.HttpContext = new DefaultHttpContext(); var actionResult = controller.Index("/") as JsonResult; - Assert.AreNotEqual(null,actionResult); + Assert.AreNotEqual(null, actionResult); var jsonCollection = actionResult?.Value as ArchiveViewModel; - Assert.AreEqual("test",jsonCollection?.FileIndexItems.FirstOrDefault()?.FileHash); + Assert.AreEqual("test", jsonCollection?.FileIndexItems.FirstOrDefault()?.FileHash); } - + [TestMethod] public void HomeControllerIndexIndexViewModel_EmptyStringPage_Test() { var fakeQuery = new FakeIQuery(new List { new FileIndexItem("/") { IsDirectory = true }, - new FileIndexItem("/test.jpg"){Tags = "test", FileHash = "test"} + new FileIndexItem("/test.jpg") { Tags = "test", FileHash = "test" } }); - - var controller = new IndexController(fakeQuery,new AppSettings()); + + var controller = new IndexController(fakeQuery, new AppSettings()); controller.ControllerContext.HttpContext = new DefaultHttpContext(); var actionResult = controller.Index(string.Empty) as JsonResult; - Assert.AreNotEqual(null,actionResult); + Assert.AreNotEqual(null, actionResult); var jsonCollection = actionResult?.Value as ArchiveViewModel; - Assert.AreEqual("test",jsonCollection?.FileIndexItems.FirstOrDefault()?.FileHash); + Assert.AreEqual("test", jsonCollection?.FileIndexItems.FirstOrDefault()?.FileHash); } [TestMethod] public void HomeControllerIndex404Test() { - var controller = new IndexController(_query,new AppSettings()); + var controller = new IndexController(_query, new AppSettings()); controller.ControllerContext.HttpContext = new DefaultHttpContext(); // Act var actionResult = controller.Index("/not-found-test") as JsonResult; Assert.AreEqual("not found", actionResult?.Value); } - + [TestMethod] public async Task Index_NoItem_Give_Success_OnHome() { await InsertSearchData(); - var controller = new IndexController(new FakeIQuery(),new AppSettings()); + var controller = new IndexController(new FakeIQuery(), new AppSettings()); controller.ControllerContext.HttpContext = new DefaultHttpContext(); var actionResult = controller.Index() as JsonResult; - Assert.AreNotEqual(null,actionResult); + Assert.AreNotEqual(null, actionResult); var jsonCollection = actionResult?.Value as ArchiveViewModel; - Assert.AreEqual(0,jsonCollection?.FileIndexItems.Count()); + Assert.AreEqual(0, jsonCollection?.FileIndexItems.Count()); } } } diff --git a/starsky/starskytest/Controllers/MetaReplaceControllerTest.cs b/starsky/starskytest/Controllers/MetaReplaceControllerTest.cs index cd3edda4c8..f6dc2324fa 100644 --- a/starsky/starskytest/Controllers/MetaReplaceControllerTest.cs +++ b/starsky/starskytest/Controllers/MetaReplaceControllerTest.cs @@ -32,7 +32,6 @@ using starsky.foundation.writemeta.Interfaces; using starskytest.FakeCreateAn; using starskytest.FakeMocks; -using starskytest.Models; namespace starskytest.Controllers { diff --git a/starsky/starskytest/Controllers/MetaUpdateControllerTest.cs b/starsky/starskytest/Controllers/MetaUpdateControllerTest.cs index 430b1a251e..1b7902d5d9 100644 --- a/starsky/starskytest/Controllers/MetaUpdateControllerTest.cs +++ b/starsky/starskytest/Controllers/MetaUpdateControllerTest.cs @@ -34,7 +34,6 @@ using starsky.foundation.writemeta.Interfaces; using starskytest.FakeCreateAn; using starskytest.FakeMocks; -using starskytest.Models; namespace starskytest.Controllers { diff --git a/starsky/starskytest/Controllers/MetricsDebugControllerTest.cs b/starsky/starskytest/Controllers/MetricsDebugControllerTest.cs index b5cef747cb..660c246ab1 100644 --- a/starsky/starskytest/Controllers/MetricsDebugControllerTest.cs +++ b/starsky/starskytest/Controllers/MetricsDebugControllerTest.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.Controllers; -using starskycore.ViewModels; +using starsky.project.web.ViewModels; using starskytest.FakeMocks; namespace starskytest.Controllers; @@ -17,6 +17,6 @@ public void MetricsDebugController_Index_test() var resultValue = jsonResult!.Value as MetricsDebugViewModel; Assert.IsNotNull(jsonResult); - Assert.AreEqual( 1d, resultValue?.CpuUsageMean); + Assert.AreEqual(1d, resultValue?.CpuUsageMean); } } diff --git a/starsky/starskytest/Controllers/PublishControllerTest.cs b/starsky/starskytest/Controllers/PublishControllerTest.cs index ed0f91bf75..df45c106be 100644 --- a/starsky/starskytest/Controllers/PublishControllerTest.cs +++ b/starsky/starskytest/Controllers/PublishControllerTest.cs @@ -19,7 +19,6 @@ using starsky.foundation.writemeta.Interfaces; using starskytest.FakeCreateAn; using starskytest.FakeMocks; -using starskytest.Models; namespace starskytest.Controllers { diff --git a/starsky/starskytest/Controllers/UploadControllerTest.cs b/starsky/starskytest/Controllers/UploadControllerTest.cs index 6a4daa761d..782b803c55 100644 --- a/starsky/starskytest/Controllers/UploadControllerTest.cs +++ b/starsky/starskytest/Controllers/UploadControllerTest.cs @@ -26,7 +26,6 @@ using starsky.foundation.worker.Services; using starskytest.FakeCreateAn; using starskytest.FakeMocks; -using starskytest.Models; namespace starskytest.Controllers { @@ -59,20 +58,20 @@ public UploadControllerTest() services.AddSingleton(new ConfigurationBuilder().Build()); // random config var createAnImage = new CreateAnImage(); - _appSettings = new AppSettings { - TempFolder = createAnImage.BasePath - }; - _query = new Query(context, _appSettings, scopeFactory, new FakeIWebLogger(), memoryCache); + _appSettings = new AppSettings { TempFolder = createAnImage.BasePath }; + _query = new Query(context, _appSettings, scopeFactory, new FakeIWebLogger(), + memoryCache); + + _iStorage = new FakeIStorage(new List { "/", "/test" }, + new List { createAnImage.DbPath }, + new List { CreateAnImage.Bytes.ToArray() }); - _iStorage = new FakeIStorage(new List{"/","/test"}, - new List{createAnImage.DbPath}, - new List{CreateAnImage.Bytes.ToArray()}); - var selectorStorage = new FakeSelectorStorage(_iStorage); _import = new Import(selectorStorage, _appSettings, new FakeIImportQuery(), - new FakeExifTool(_iStorage,_appSettings), _query, new ConsoleWrapper(), - new FakeIMetaExifThumbnailService(), new FakeIWebLogger(), new FakeIThumbnailQuery(), memoryCache); + new FakeExifTool(_iStorage, _appSettings), _query, new ConsoleWrapper(), + new FakeIMetaExifThumbnailService(), new FakeIWebLogger(), + new FakeIThumbnailQuery(), memoryCache); // Start using dependency injection // Add random config to dependency injection @@ -86,10 +85,10 @@ public UploadControllerTest() // build the service var serviceProvider = services.BuildServiceProvider(); // get the service - + serviceProvider.GetRequiredService(); } - + /// /// Add the file in the underlying request object. /// @@ -101,8 +100,9 @@ private static ControllerContext RequestWithFile(byte[]? bytes = null) var httpContext = new DefaultHttpContext(); httpContext.Request.Headers.Append("Content-Type", "application/octet-stream"); httpContext.Request.Body = new MemoryStream(bytes); - - var actionContext = new ActionContext(httpContext, new RouteData(), new ControllerActionDescriptor()); + + var actionContext = new ActionContext(httpContext, new RouteData(), + new ControllerActionDescriptor()); return new ControllerContext(actionContext); } @@ -110,24 +110,25 @@ private static ControllerContext RequestWithFile(byte[]? bytes = null) public async Task UploadToFolder_NoToHeader_BadRequest() { var controller = - new UploadController(_import, _appSettings, - new FakeSelectorStorage(new FakeIStorage()), _query, + new UploadController(_import, _appSettings, + new FakeSelectorStorage(new FakeIStorage()), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) + new FakeIMetaExifThumbnailService(), + new FakeIMetaUpdateStatusThumbnailService()) { - ControllerContext = {HttpContext = new DefaultHttpContext()} + ControllerContext = { HttpContext = new DefaultHttpContext() } }; - - var actionResult = await controller.UploadToFolder()as BadRequestObjectResult; - - Assert.AreEqual(400,actionResult?.StatusCode); + + var actionResult = await controller.UploadToFolder() as BadRequestObjectResult; + + Assert.AreEqual(400, actionResult?.StatusCode); } - + [TestMethod] public async Task UploadToFolder_DefaultFlow() { - var controller = new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { @@ -135,28 +136,29 @@ public async Task UploadToFolder_DefaultFlow() }; const string toPlaceSubPath = "/yes01.jpg"; - - controller.ControllerContext.HttpContext.Request.Headers["to"] = toPlaceSubPath; //Set header - var actionResult = await controller.UploadToFolder() as JsonResult; + controller.ControllerContext.HttpContext.Request.Headers["to"] = + toPlaceSubPath; //Set header + + var actionResult = await controller.UploadToFolder() as JsonResult; var list = actionResult?.Value as List; - Assert.AreEqual( ImportStatus.Ok, list?.FirstOrDefault()?.Status); + Assert.AreEqual(ImportStatus.Ok, list?.FirstOrDefault()?.Status); var fileSystemResult = _iStorage.ExistFile(toPlaceSubPath); Assert.IsTrue(fileSystemResult); var queryResult = _query.SingleItem(toPlaceSubPath); - Assert.AreEqual("Sony",queryResult?.FileIndexItem?.Make); + Assert.AreEqual("Sony", queryResult?.FileIndexItem?.Make); await _query.RemoveItemAsync(queryResult?.FileIndexItem!); } - + [TestMethod] public async Task UploadToFolder_DefaultFlow_ColorClass() { - var controller = new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { @@ -164,30 +166,31 @@ public async Task UploadToFolder_DefaultFlow_ColorClass() }; const string toPlaceSubPath = "/color-class01.jpg"; - - controller.ControllerContext.HttpContext.Request.Headers["to"] = toPlaceSubPath; //Set header - var actionResult = await controller.UploadToFolder() as JsonResult; + controller.ControllerContext.HttpContext.Request.Headers["to"] = + toPlaceSubPath; //Set header + + var actionResult = await controller.UploadToFolder() as JsonResult; var list = actionResult?.Value as List; - Assert.AreEqual( ImportStatus.Ok, list?.FirstOrDefault()?.Status); + Assert.AreEqual(ImportStatus.Ok, list?.FirstOrDefault()?.Status); var fileSystemResult = _iStorage.ExistFile(toPlaceSubPath); Assert.IsTrue(fileSystemResult); var queryResult = _query.SingleItem(toPlaceSubPath); - - Assert.AreEqual("Sony",queryResult?.FileIndexItem?.Make); - Assert.AreEqual(ColorClassParser.Color.Winner,queryResult?.FileIndexItem?.ColorClass); + + Assert.AreEqual("Sony", queryResult?.FileIndexItem?.Make); + Assert.AreEqual(ColorClassParser.Color.Winner, queryResult?.FileIndexItem?.ColorClass); await _query.RemoveItemAsync(queryResult?.FileIndexItem!); } - + [TestMethod] public async Task UploadToFolder_DefaultFlow_ShouldNotOverWriteDatabase() { - var controller = new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { @@ -199,23 +202,25 @@ public async Task UploadToFolder_DefaultFlow_ShouldNotOverWriteDatabase() // add to db await _query.AddItemAsync(new FileIndexItem(toPlaceSubPath)); - + _iStorage.CreateDirectory(toPlaceFolder); - - controller.ControllerContext.HttpContext.Request.Headers["to"] = toPlaceSubPath; //Set header + + controller.ControllerContext.HttpContext.Request.Headers["to"] = + toPlaceSubPath; //Set header var actionResult = await controller.UploadToFolder() as JsonResult; - if ( actionResult == null ) { + if ( actionResult == null ) + { throw new WebException("actionResult should not be null"); } - + var list = actionResult.Value as List; if ( list == null ) { throw new WebException("list should not be null"); } - Assert.AreEqual( ImportStatus.Ok, list[0].Status); + Assert.AreEqual(ImportStatus.Ok, list[0].Status); var fileSystemResult = _iStorage.ExistFile(toPlaceSubPath); Assert.IsTrue(fileSystemResult); @@ -223,19 +228,19 @@ public async Task UploadToFolder_DefaultFlow_ShouldNotOverWriteDatabase() var getAllFiles = await _query.GetAllFilesAsync(toPlaceFolder); // Should not duplicate - Assert.AreEqual(1,getAllFiles.Count); - + Assert.AreEqual(1, getAllFiles.Count); + var queryResult = _query.SingleItem(toPlaceSubPath); - Assert.AreEqual("Sony",queryResult?.FileIndexItem?.Make); + Assert.AreEqual("Sony", queryResult?.FileIndexItem?.Make); await _query.RemoveItemAsync(queryResult?.FileIndexItem!); } - + [TestMethod] public async Task UploadToFolder_SidecarListShouldBeUpdated() { - var controller = new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { @@ -246,55 +251,58 @@ public async Task UploadToFolder_SidecarListShouldBeUpdated() var toPlaceXmp = "/test_sidecar.xmp"; await _iStorage.WriteStreamAsync(new MemoryStream(new byte[1]), toPlaceXmp); - - controller.ControllerContext.HttpContext.Request.Headers["to"] = toPlaceSubPath; //Set header + + controller.ControllerContext.HttpContext.Request.Headers["to"] = + toPlaceSubPath; //Set header await controller.UploadToFolder(); var queryResult = _query.SingleItem(toPlaceSubPath); var sidecarExtList = queryResult?.FileIndexItem?.SidecarExtensionsList.ToList(); - Assert.AreEqual(1,sidecarExtList?.Count); - Assert.AreEqual("xmp",sidecarExtList?[0]); + Assert.AreEqual(1, sidecarExtList?.Count); + Assert.AreEqual("xmp", sidecarExtList?[0]); await _query.RemoveItemAsync(queryResult?.FileIndexItem!); } - + [TestMethod] public async Task UploadToFolder_NotFound() { var controller = - new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, + new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) + new FakeIMetaExifThumbnailService(), + new FakeIMetaUpdateStatusThumbnailService()) { ControllerContext = RequestWithFile(), }; - controller.ControllerContext.HttpContext.Request.Headers["to"] = "/not-found"; //Set header + controller.ControllerContext.HttpContext.Request.Headers["to"] = + "/not-found"; //Set header - var actionResult = await controller.UploadToFolder()as NotFoundObjectResult; - - Assert.AreEqual(404,actionResult?.StatusCode); + var actionResult = await controller.UploadToFolder() as NotFoundObjectResult; + + Assert.AreEqual(404, actionResult?.StatusCode); } - + [TestMethod] public async Task UploadToFolder_UnknownFailFlow() { - var controller = new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { ControllerContext = RequestWithFile(), }; - + controller.ControllerContext.HttpContext.Request.Headers["to"] = "/"; //Set header - var actionResult = await controller.UploadToFolder() as JsonResult; + var actionResult = await controller.UploadToFolder() as JsonResult; var list = actionResult?.Value as List; - Assert.AreEqual( ImportStatus.FileError, list?.FirstOrDefault()?.Status); + Assert.AreEqual(ImportStatus.FileError, list?.FirstOrDefault()?.Status); } [TestMethod] @@ -302,9 +310,9 @@ public void GetParentDirectoryFromRequestHeader_InputToAsSubPath() { var controllerContext = RequestWithFile(); controllerContext.HttpContext.Request.Headers.Append("to", "/test.jpg"); - - var controller = new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, + + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { @@ -314,15 +322,15 @@ public void GetParentDirectoryFromRequestHeader_InputToAsSubPath() var result = controller.GetParentDirectoryFromRequestHeader(); Assert.AreEqual("/", result); } - + [TestMethod] public void GetParentDirectoryFromRequestHeader_InputToAsSubPath_TestFolder() { var controllerContext = RequestWithFile(); controllerContext.HttpContext.Request.Headers.Append("to", "/test/test.jpg"); - - var controller = new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, + + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { @@ -332,15 +340,15 @@ public void GetParentDirectoryFromRequestHeader_InputToAsSubPath_TestFolder() var result = controller.GetParentDirectoryFromRequestHeader(); Assert.AreEqual("/test", result); } - + [TestMethod] public void GetParentDirectoryFromRequestHeader_InputToAsSubPath_TestDirectFolder() { var controllerContext = RequestWithFile(); controllerContext.HttpContext.Request.Headers.Append("to", "/test/"); - - var controller = new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, + + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { @@ -350,7 +358,7 @@ public void GetParentDirectoryFromRequestHeader_InputToAsSubPath_TestDirectFolde var result = controller.GetParentDirectoryFromRequestHeader(); Assert.AreEqual("/test", result); } - + [TestMethod] public void GetParentDirectoryFromRequestHeader_InputToAsSubPath_NonExistFolder() { @@ -358,18 +366,19 @@ public void GetParentDirectoryFromRequestHeader_InputToAsSubPath_NonExistFolder( controllerContext.HttpContext.Request.Headers.Append("to", "/non-exist/test.jpg"); var controller = - new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, + new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) + new FakeIMetaExifThumbnailService(), + new FakeIMetaUpdateStatusThumbnailService()) { ControllerContext = controllerContext }; - + var result = controller.GetParentDirectoryFromRequestHeader(); Assert.IsNull(result); } - + /// /// Add the file in the underlying request object. /// @@ -379,16 +388,17 @@ private static ControllerContext RequestWithSidecar() var httpContext = new DefaultHttpContext(); httpContext.Request.Headers.Append("Content-Type", "application/octet-stream"); httpContext.Request.Body = new MemoryStream(CreateAnXmp.Bytes.ToArray()); - - var actionContext = new ActionContext(httpContext, new RouteData(), new ControllerActionDescriptor()); + + var actionContext = new ActionContext(httpContext, new RouteData(), + new ControllerActionDescriptor()); return new ControllerContext(actionContext); } - + [TestMethod] public async Task UploadToFolderSidecarFile_DefaultFlow() { - var controller = new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { @@ -396,20 +406,22 @@ public async Task UploadToFolderSidecarFile_DefaultFlow() }; var toPlaceSubPath = "/yes01.xmp"; - controller.ControllerContext.HttpContext.Request.Headers["to"] = toPlaceSubPath; //Set header + controller.ControllerContext.HttpContext.Request.Headers["to"] = + toPlaceSubPath; //Set header - var actionResult = await controller.UploadToFolderSidecarFile() as JsonResult; + var actionResult = await controller.UploadToFolderSidecarFile() as JsonResult; var list = actionResult?.Value as List; Assert.AreEqual(toPlaceSubPath, list?.FirstOrDefault()); } - + [TestMethod] public async Task UploadToFolderSidecarFile_UpdateMainItemWithSidecarRef() { // it should add a reference to the main item - var controller = new UploadController(_import, new AppSettings{UseDiskWatcher = false}, - new FakeSelectorStorage(_iStorage), _query, + var controller = new UploadController(_import, + new AppSettings { UseDiskWatcher = false }, + new FakeSelectorStorage(_iStorage), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { @@ -419,23 +431,24 @@ public async Task UploadToFolderSidecarFile_UpdateMainItemWithSidecarRef() var dngSubPath = "/UploadToFolderSidecarFile.dng"; await _query.AddItemAsync( new FileIndexItem(dngSubPath)); - + var toPlaceSubPath = "/UploadToFolderSidecarFile.xmp"; - controller.ControllerContext.HttpContext.Request.Headers["to"] = toPlaceSubPath; //Set header + controller.ControllerContext.HttpContext.Request.Headers["to"] = + toPlaceSubPath; //Set header await controller.UploadToFolderSidecarFile(); var queryResult = await _query.GetObjectByFilePathAsync(dngSubPath); var sidecarExtList = queryResult?.SidecarExtensionsList.ToList(); - Assert.AreEqual(1,sidecarExtList?.Count); - Assert.AreEqual("xmp",sidecarExtList?[0]); + Assert.AreEqual(1, sidecarExtList?.Count); + Assert.AreEqual("xmp", sidecarExtList?[0]); } - + [TestMethod] public async Task UploadToFolderSidecarFile_NoXml_SoIgnore() { - var controller = new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, + var controller = new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) { @@ -443,47 +456,52 @@ public async Task UploadToFolderSidecarFile_NoXml_SoIgnore() }; var toPlaceSubPath = "/yes01.xmp"; - controller.ControllerContext.HttpContext.Request.Headers["to"] = toPlaceSubPath; //Set header + controller.ControllerContext.HttpContext.Request.Headers["to"] = + toPlaceSubPath; //Set header - var actionResult = await controller.UploadToFolderSidecarFile() as JsonResult; + var actionResult = await controller.UploadToFolderSidecarFile() as JsonResult; var list = actionResult?.Value as List; Assert.AreEqual(0, list?.Count); } - + [TestMethod] public async Task UploadToFolderSidecarFile_NotFound() { var controller = - new UploadController(_import, _appSettings, - new FakeSelectorStorage(_iStorage), _query, + new UploadController(_import, _appSettings, + new FakeSelectorStorage(_iStorage), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) + new FakeIMetaExifThumbnailService(), + new FakeIMetaUpdateStatusThumbnailService()) { ControllerContext = RequestWithFile(), }; - controller.ControllerContext.HttpContext.Request.Headers["to"] = "/not-found"; //Set header + controller.ControllerContext.HttpContext.Request.Headers["to"] = + "/not-found"; //Set header + + var actionResult = await controller.UploadToFolderSidecarFile() as NotFoundObjectResult; - var actionResult = await controller.UploadToFolderSidecarFile()as NotFoundObjectResult; - - Assert.AreEqual(404,actionResult?.StatusCode); + Assert.AreEqual(404, actionResult?.StatusCode); } - + [TestMethod] public async Task UploadToFolderSidecarFile_NoToHeader_BadRequest() { var controller = - new UploadController(_import, _appSettings, - new FakeSelectorStorage(new FakeIStorage()), _query, + new UploadController(_import, _appSettings, + new FakeSelectorStorage(new FakeIStorage()), _query, new FakeIRealtimeConnectionsService(), new FakeIWebLogger(), - new FakeIMetaExifThumbnailService(), new FakeIMetaUpdateStatusThumbnailService()) + new FakeIMetaExifThumbnailService(), + new FakeIMetaUpdateStatusThumbnailService()) { - ControllerContext = {HttpContext = new DefaultHttpContext()} + ControllerContext = { HttpContext = new DefaultHttpContext() } }; - - var actionResult = await controller.UploadToFolderSidecarFile()as BadRequestObjectResult; - - Assert.AreEqual(400,actionResult?.StatusCode); + + var actionResult = + await controller.UploadToFolderSidecarFile() as BadRequestObjectResult; + + Assert.AreEqual(400, actionResult?.StatusCode); } } } diff --git a/starsky/starskytest/Models/FakeExifTool.cs b/starsky/starskytest/FakeMocks/FakeExifTool.cs similarity index 60% rename from starsky/starskytest/Models/FakeExifTool.cs rename to starsky/starskytest/FakeMocks/FakeExifTool.cs index 074bcf44c7..5cb7ede671 100644 --- a/starsky/starskytest/Models/FakeExifTool.cs +++ b/starsky/starskytest/FakeMocks/FakeExifTool.cs @@ -8,7 +8,7 @@ using starsky.foundation.storage.Services; using starsky.foundation.writemeta.Interfaces; -namespace starskytest.Models +namespace starskytest.FakeMocks { public sealed class FakeExifTool : IExifToolHostStorage { @@ -19,13 +19,16 @@ public FakeExifTool(IStorage iStorage, AppSettings _) { _iStorage = iStorage; } - - public const string XmpInjection = "" + - "\n\n" + - "\n \n \n " + - " \n " + "test\n \n \n \n\n" + - " \n " + - "kamer\n \n\n\n"; + + public const string XmpInjection = + "" + + "\n\n" + + "\n \n \n " + + " \n " + + "test\n \n \n \n\n" + + " \n " + + "kamer\n \n\n\n"; public async Task WriteTagsAsync(string subPath, string command) { @@ -36,10 +39,12 @@ public async Task WriteTagsAsync(string subPath, string command) var stream = StringToStreamHelper.StringToStream(XmpInjection); await _iStorage.WriteStreamAsync(stream, subPath); } + return true; } - public async Task> WriteTagsAndRenameThumbnailAsync(string subPath, string? beforeFileHash, + public async Task> WriteTagsAndRenameThumbnailAsync( + string subPath, string? beforeFileHash, string command, CancellationToken cancellationToken = default) { Console.WriteLine("Fake ExifTool + " + subPath + " " + command); @@ -49,8 +54,8 @@ public async Task> WriteTagsAndRenameThumbnailAsync(s var stream = StringToStreamHelper.StringToStream(XmpInjection); await _iStorage.WriteStreamAsync(stream, subPath); } - - var newFileHash = (await new FileHash(_iStorage).GetHashCodeAsync(subPath)).Key; + + var newFileHash = ( await new FileHash(_iStorage).GetHashCodeAsync(subPath) ).Key; return new KeyValuePair(true, newFileHash); } diff --git a/starsky/starskytest/Helpers/MimeHelperTest.cs b/starsky/starskytest/Helpers/MimeHelperTest.cs deleted file mode 100644 index d72b54b11c..0000000000 --- a/starsky/starskytest/Helpers/MimeHelperTest.cs +++ /dev/null @@ -1,46 +0,0 @@ -ο»Ώusing Microsoft.VisualStudio.TestTools.UnitTesting; -using starskycore.Helpers; - -namespace starskytest.Helpers -{ - [TestClass] - public sealed class MimeHelperTest - { - [TestMethod] - public void GetMimeTypeByFileNameTestUnknown() - { - Assert.AreEqual("application/octet-stream",MimeHelper.GetMimeTypeByFileName("test.unknown")); - } - - [TestMethod] - public void GetMimeTypeByFileNameTestJpg() - { - Assert.AreEqual("image/jpeg",MimeHelper.GetMimeTypeByFileName("test.jpg")); - } - - [TestMethod] - public void GetMimeTypeByFileNameTestJpeg() - { - Assert.AreEqual("image/jpeg",MimeHelper.GetMimeTypeByFileName("test.jpeg")); - } - - [TestMethod] - public void GetMimeTypeByExtensionTest_NoExtension() - { - Assert.AreEqual("application/octet-stream",MimeHelper.GetMimeTypeByFileName(string.Empty)); - } - - [TestMethod] - public void GetMimeType_NoExtension() - { - Assert.AreEqual("application/octet-stream",MimeHelper.GetMimeType(string.Empty)); - } - - - [TestMethod] - public void GetMimeType_Jpeg() - { - Assert.AreEqual("image/jpeg",MimeHelper.GetMimeType("jpg")); - } - } -} diff --git a/starsky/starskytest/Models/Account/CredentialTypeTest.cs b/starsky/starskytest/Models/Account/CredentialTypeTest.cs deleted file mode 100644 index e89c40d593..0000000000 --- a/starsky/starskytest/Models/Account/CredentialTypeTest.cs +++ /dev/null @@ -1,27 +0,0 @@ -ο»Ώusing System.Collections.Generic; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using starsky.foundation.database.Models.Account; - -namespace starskytest.Models.Account -{ - [TestClass] - public sealed class CredentialTypeTest - { - [TestMethod] - public void CredentialTypeSetup_Test() - { - var creds = new CredentialType - { - Id = 0, - Code = string.Empty, - Name = string.Empty, - Position = 0, - Credentials = new List() - }; - Assert.AreEqual(0, creds.Id); - Assert.AreEqual(0, creds.Position); - Assert.AreEqual(string.Empty, creds.Code); - - } - } -} diff --git a/starsky/starskytest/Models/Account/PermissionTest.cs b/starsky/starskytest/Models/Account/PermissionTest.cs deleted file mode 100644 index 5dcbd14757..0000000000 --- a/starsky/starskytest/Models/Account/PermissionTest.cs +++ /dev/null @@ -1,25 +0,0 @@ -ο»Ώusing Microsoft.VisualStudio.TestTools.UnitTesting; -using starsky.foundation.database.Models.Account; - -namespace starskytest.Models.Account -{ - [TestClass] - public sealed class PermissionTest - { - [TestMethod] - public void CredentialSetupTest() - { - var creds = new Permission - { - Id = 0, - Code = string.Empty, - Name = string.Empty, - Position = 0 - }; - Assert.AreEqual(0, creds.Id); - Assert.AreEqual(0, creds.Position); - Assert.AreEqual(string.Empty, creds.Code); - - } - } -} diff --git a/starsky/starskytest/Models/Account/RolePermissionTest.cs b/starsky/starskytest/Models/Account/RolePermissionTest.cs deleted file mode 100644 index 8b29e4e733..0000000000 --- a/starsky/starskytest/Models/Account/RolePermissionTest.cs +++ /dev/null @@ -1,24 +0,0 @@ -ο»Ώusing Microsoft.VisualStudio.TestTools.UnitTesting; -using starsky.foundation.database.Models.Account; - -namespace starskytest.Models.Account -{ - [TestClass] - public sealed class RolePermissionTest - { - [TestMethod] - public void RolePermissionSetupTest() - { - // RoleId + PermissionId - var creds = new RolePermission - { - RoleId = 0, - PermissionId = 0, - Role = new Role(), - Permission = new Permission() - }; - Assert.AreEqual(0, creds.RoleId); - Assert.AreEqual(0, creds.PermissionId); - } - } -} diff --git a/starsky/starskytest/Models/Account/UserRoleTest.cs b/starsky/starskytest/Models/Account/UserRoleTest.cs deleted file mode 100644 index 7a5f9f7fcc..0000000000 --- a/starsky/starskytest/Models/Account/UserRoleTest.cs +++ /dev/null @@ -1,23 +0,0 @@ -ο»Ώusing Microsoft.VisualStudio.TestTools.UnitTesting; -using starsky.foundation.database.Models.Account; - -namespace starskytest.Models.Account -{ - [TestClass] - public sealed class UserRoleTest - { - [TestMethod] - public void UserRoleTest_SetupTest() - { - var role = new UserRole() - { - UserId = 0, - RoleId = 0, - User = new User(), - Role = new Role() - }; - Assert.AreEqual(0, role.UserId); - Assert.AreEqual(0, role.RoleId); - } - } -} diff --git a/starsky/starskytest/Models/FolderOrFileModelTest.cs b/starsky/starskytest/Models/FolderOrFileModelTest.cs deleted file mode 100644 index cba184cc58..0000000000 --- a/starsky/starskytest/Models/FolderOrFileModelTest.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using starsky.foundation.storage.Models; - -namespace starskytest.Models -{ - [TestClass] - public sealed class FolderOrFileModelTest - { - [TestMethod] - public void FolderOrFileModelFolderOrFileTypeListTest() - { - var ToSearchType = FolderOrFileModel.FolderOrFileTypeList.Folder; - var folderOrFileModel = new FolderOrFileModel - { - IsFolderOrFile = ToSearchType - }; - Assert.AreEqual(folderOrFileModel.IsFolderOrFile,ToSearchType); - } - - } -} diff --git a/starsky/starskytest/Models/SearchViewModelTest.cs b/starsky/starskytest/Models/SearchViewModelTest.cs deleted file mode 100644 index 2b3a9cd088..0000000000 --- a/starsky/starskytest/Models/SearchViewModelTest.cs +++ /dev/null @@ -1,261 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using starsky.feature.search.ViewModels; -using starsky.foundation.database.Models; - -namespace starskytest.Models -{ - [TestClass] - public sealed class SearchViewModelTest - { - [TestMethod] - public void SearchViewModel_ElapsedSeconds_Test() - { - var searchViewModel = new SearchViewModel{ElapsedSeconds = 0.0006}; - Assert.AreEqual(true, searchViewModel.ElapsedSeconds <= 0.001); - } - - [TestMethod] - public void SearchViewModel_Offset_Test() - { - var searchViewModel = new SearchViewModel(); - Assert.AreEqual(0,Math.Floor(searchViewModel.Offset)); - } - - [TestMethod] - public void PropertySearchTest() - { - var property = new FileIndexItem{Tags = "q"}.GetType().GetProperty(nameof(FileIndexItem.Tags))!; - - // not a great test - var search = SearchViewModel.PropertySearch(new SearchViewModel{SearchFor = { "q" }}, property, - "q", SearchViewModel.SearchForOptionType.Equal); - - Assert.AreEqual(0, search.CollectionsCount); - } - - [TestMethod] - public void PropertySearchStringType_DefaultCase_NullConditions1() - { - // Arrange - var model = new SearchViewModel - { - FileIndexItems = null - }; - - var property = typeof(FileIndexItem).GetProperty("NotFound"); - Assert.IsNull(property); - - const string searchForQuery = "file"; - var searchType = SearchViewModel.SearchForOptionType.Equal; - - // Act - var result = SearchViewModel.PropertySearchStringType(model, property!, searchForQuery, searchType); - - // Assert - Assert.IsNotNull(result); - Assert.IsNull(result.FileIndexItems); - } - - [TestMethod] - public void PropertySearchStringType_DefaultCase_NullConditions2() - { - // Arrange - var model = new SearchViewModel - { - FileIndexItems = null - }; - - var property = typeof(FileIndexItem).GetProperty(nameof(FileIndexItem.Tags)); - const string searchForQuery = "file"; - var searchType = SearchViewModel.SearchForOptionType.Equal; - - // Act - var result = SearchViewModel.PropertySearchStringType(model, property!, searchForQuery, searchType); - - // Assert - Assert.IsNotNull(result); - Assert.IsNull(result.FileIndexItems); - } - - [TestMethod] - public void PropertySearchStringType_DefaultCase_Found_Null() - { - // Arrange - var model = new SearchViewModel - { - FileIndexItems = new List{new FileIndexItem("test"){LocationCity = null}} - }; - - var property = typeof(FileIndexItem).GetProperty(nameof(FileIndexItem.LocationCity)); - const string searchForQuery = "file"; - var searchType = SearchViewModel.SearchForOptionType.Equal; - - // Act - var result = SearchViewModel.PropertySearchStringType(model, property!, searchForQuery, searchType); - - // Assert - Assert.IsNotNull(result); - Assert.AreEqual(0,result.FileIndexItems?.Count); - } - - [TestMethod] - public void PropertySearchStringType_DefaultCase_Found_HappyFlow() - { - // Arrange - var model = new SearchViewModel - { - FileIndexItems = new List{new FileIndexItem("test"){LocationCity = "test"}} - }; - - var property = typeof(FileIndexItem).GetProperty(nameof(FileIndexItem.LocationCity)); - const string searchForQuery = "test"; - var searchType = SearchViewModel.SearchForOptionType.Equal; - - // Act - var result = SearchViewModel.PropertySearchStringType(model, property!, searchForQuery, searchType); - - // Assert - Assert.IsNotNull(result); - Assert.AreEqual(1,result.FileIndexItems?.Count); - } - - [TestMethod] - public void PropertySearchBoolType_FiltersItemsByBoolProperty() - { - // Arrange - var model = new SearchViewModel(); - model.FileIndexItems = new List - { - new FileIndexItem { IsDirectory = true }, - new FileIndexItem { IsDirectory = false }, - new FileIndexItem { IsDirectory = true }, - }; - var property = typeof(FileIndexItem).GetProperty("IsDirectory"); - var boolIsValue = true; - - // Act - var result = SearchViewModel.PropertySearchBoolType(model, property, boolIsValue); - - // Assert - Assert.AreEqual(2, result.FileIndexItems?.Count); - Assert.IsTrue(result.FileIndexItems?.Exists(item => item.IsDirectory == true)); - } - - [TestMethod] - public void PropertySearchBoolType_WithNullModel_ReturnsNullModel() - { - // Arrange - SearchViewModel? model = null; - var property = typeof(FileIndexItem).GetProperty("IsDirectory"); - const bool boolIsValue = true; - - // Act - var result = SearchViewModel.PropertySearchBoolType(model, property, boolIsValue); - - // Assert - Assert.IsNotNull(result); - } - - [TestMethod] - public void PropertySearchBoolType_WithNullFileIndexItems_ReturnsNullFileIndexItems() - { - // Arrange - var model = new SearchViewModel - { - FileIndexItems = null, - }; - var property = typeof(FileIndexItem).GetProperty("IsDirectory"); - var boolIsValue = true; - - // Act - var result = SearchViewModel.PropertySearchBoolType(model, property, boolIsValue); - - // Assert - Assert.IsNull(result.FileIndexItems); - } - - [TestMethod] - public void PropertySearchBoolType_WithEmptyFileIndexItems_ReturnsEmptyFileIndexItems() - { - // Arrange - var model = new SearchViewModel - { - FileIndexItems = new List(), - }; - var property = typeof(FileIndexItem).GetProperty("IsDirectory"); - var boolIsValue = true; - - // Act - var result = SearchViewModel.PropertySearchBoolType(model, property, boolIsValue); - - // Assert - Assert.IsNotNull(result.FileIndexItems); - Assert.AreEqual(0, result.FileIndexItems.Count); - } - - [TestMethod] - public void PropertySearchBoolType_WithInvalidProperty_ReturnsOriginalModel() - { - // Arrange - var model = new SearchViewModel(); - model.FileIndexItems = new List - { - new FileIndexItem { IsDirectory = true }, - }; - var property = typeof(FileIndexItem).GetProperty("NonExistentProperty"); - var boolIsValue = true; - - // Act - var result = SearchViewModel.PropertySearchBoolType(model, property, boolIsValue); - - // Assert - Assert.AreEqual(model, result); - } - - [TestMethod] - public void PropertySearch_WithBoolPropertyAndValidBoolValue_ReturnsFilteredModel() - { - // Arrange - var model = new SearchViewModel { FileIndexItems = new List - { - new FileIndexItem { IsDirectory = true }, - new FileIndexItem { IsDirectory = false }, - new FileIndexItem { IsDirectory = true }, - } - }; - var property = typeof(FileIndexItem).GetProperty("IsDirectory"); - const string searchForQuery = "true"; - const SearchViewModel.SearchForOptionType searchType = SearchViewModel.SearchForOptionType.Equal; - - // Act - var result = SearchViewModel.PropertySearch(model, property!, searchForQuery, searchType); - - // Assert - Assert.AreEqual(2, result.FileIndexItems?.Count); - Assert.IsTrue(result.FileIndexItems?.Exists(item => item.IsDirectory == true)); - } - - [TestMethod] - public void PropertySearch_WithBoolPropertyAndInvalidBoolValue_ReturnsOriginalModel() - { - // Arrange - var model = new SearchViewModel(); - model.FileIndexItems = new List - { - new FileIndexItem { IsDirectory = true }, - new FileIndexItem { IsDirectory = false }, - }; - var property = typeof(FileIndexItem).GetProperty("IsDirectory"); - const string searchForQuery = "invalid_bool_value"; // An invalid boolean string - const SearchViewModel.SearchForOptionType searchType = SearchViewModel.SearchForOptionType.Equal; // You can set this as needed - - // Act - var result = SearchViewModel.PropertySearch(model, property!, searchForQuery, searchType); - - // Assert - CollectionAssert.AreEqual(model.FileIndexItems, result.FileIndexItems); - } - } -} diff --git a/starsky/starskytest/ViewModels/ArchiveViewModelTest.cs b/starsky/starskytest/ViewModels/ArchiveViewModelTest.cs index 9cceac0f0b..a8e841c099 100644 --- a/starsky/starskytest/ViewModels/ArchiveViewModelTest.cs +++ b/starsky/starskytest/ViewModels/ArchiveViewModelTest.cs @@ -3,7 +3,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.foundation.database.Models; using starsky.foundation.platform.Helpers; -using starskycore.ViewModels; +using starsky.project.web.ViewModels; namespace starskytest.ViewModels { @@ -13,47 +13,48 @@ public sealed class ArchiveViewModelTest [TestMethod] public void ArchiveViewModelPageTypeTest() { - var viewModel = new ArchiveViewModel(); - Assert.AreEqual( PageViewType.PageType.Archive.ToString(),viewModel.PageType); + var viewModel = new ArchiveViewModel(); + Assert.AreEqual(PageViewType.PageType.Archive.ToString(), viewModel.PageType); } - + [TestMethod] public void ArchiveViewModel_ColorClass() { var viewModel = new ArchiveViewModel { - ColorClassActiveList = new List{ColorClassParser.Color.None}, - ColorClassUsage = new List{ColorClassParser.Color.None} + ColorClassActiveList = + new List { ColorClassParser.Color.None }, + ColorClassUsage = + new List { ColorClassParser.Color.None } }; - - Assert.AreEqual(ColorClassParser.Color.None, viewModel.ColorClassActiveList.FirstOrDefault()); - Assert.AreEqual(ColorClassParser.Color.None, viewModel.ColorClassUsage.FirstOrDefault()); + + Assert.AreEqual(ColorClassParser.Color.None, + viewModel.ColorClassActiveList.FirstOrDefault()); + Assert.AreEqual(ColorClassParser.Color.None, + viewModel.ColorClassUsage.FirstOrDefault()); } - + [TestMethod] public void ArchiveViewModel_ExampleData() { var archiveViewModel = new ArchiveViewModel { FileIndexItems = new List(), - Breadcrumb = new List{"/"}, - RelativeObjects = new RelativeObjects - { - NextFilePath = "/" - }, + Breadcrumb = new List { "/" }, + RelativeObjects = new RelativeObjects { NextFilePath = "/" }, SearchQuery = "test", SubPath = "/", IsReadOnly = false, - CollectionsCount= 0, + CollectionsCount = 0, Collections = true, }; - - Assert.AreEqual("/", archiveViewModel.Breadcrumb.FirstOrDefault()); - Assert.AreEqual("/", archiveViewModel.RelativeObjects.NextFilePath); - Assert.AreEqual("test", archiveViewModel.SearchQuery); - Assert.AreEqual("/", archiveViewModel.SubPath); - Assert.AreEqual(false, archiveViewModel.IsReadOnly); - Assert.AreEqual(0, archiveViewModel.CollectionsCount); + + Assert.AreEqual("/", archiveViewModel.Breadcrumb.FirstOrDefault()); + Assert.AreEqual("/", archiveViewModel.RelativeObjects.NextFilePath); + Assert.AreEqual("test", archiveViewModel.SearchQuery); + Assert.AreEqual("/", archiveViewModel.SubPath); + Assert.AreEqual(false, archiveViewModel.IsReadOnly); + Assert.AreEqual(0, archiveViewModel.CollectionsCount); Assert.IsTrue(archiveViewModel.Collections); } } diff --git a/starsky/starskytest/ViewModels/SyncViewModelTest.cs b/starsky/starskytest/ViewModels/SyncViewModelTest.cs index 28dbbeedff..87ac0886cb 100644 --- a/starsky/starskytest/ViewModels/SyncViewModelTest.cs +++ b/starsky/starskytest/ViewModels/SyncViewModelTest.cs @@ -1,6 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.foundation.database.Models; -using starskycore.ViewModels; +using starsky.project.web.ViewModels; namespace starskytest.ViewModels { @@ -12,12 +12,11 @@ public void SyncViewModelSyncViewModelTest() { var syncViewModel = new SyncViewModel { - FilePath = "/test", - Status = FileIndexItem.ExifStatus.Ok - }; - - Assert.AreEqual("/test",syncViewModel.FilePath); - Assert.AreEqual(FileIndexItem.ExifStatus.Ok,syncViewModel.Status); + FilePath = "/test", Status = FileIndexItem.ExifStatus.Ok + }; + + Assert.AreEqual("/test", syncViewModel.FilePath); + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, syncViewModel.Status); } } } diff --git a/starsky/starskytest/starsky.feature.geolookup/Services/GeoLocationWriteTest.cs b/starsky/starskytest/starsky.feature.geolookup/Services/GeoLocationWriteTest.cs index 000c4d49f4..74fdcd18c0 100644 --- a/starsky/starskytest/starsky.feature.geolookup/Services/GeoLocationWriteTest.cs +++ b/starsky/starskytest/starsky.feature.geolookup/Services/GeoLocationWriteTest.cs @@ -7,7 +7,6 @@ using starsky.foundation.writemeta.Interfaces; using starsky.foundation.writemeta.Services; using starskytest.FakeMocks; -using starskytest.Models; namespace starskytest.starsky.feature.geolookup.Services { @@ -21,7 +20,7 @@ public GeoLocationWriteTest() { // get the service _appSettings = new AppSettings(); - _exifTool = new FakeExifTool(new FakeIStorage(),_appSettings ); + _exifTool = new FakeExifTool(new FakeIStorage(), _appSettings); } [TestMethod] @@ -44,13 +43,14 @@ public async Task GeoLocationWriteLoopFolderTest() var console = new FakeConsoleWrapper(); var fakeIStorage = new FakeIStorage(); - await new GeoLocationWrite(_appSettings, _exifTool, - new FakeSelectorStorage(fakeIStorage),console, - new FakeIWebLogger(), new FakeIThumbnailQuery()).LoopFolderAsync(metaFilesInDirectory, true); + await new GeoLocationWrite(_appSettings, _exifTool, + new FakeSelectorStorage(fakeIStorage), console, + new FakeIWebLogger(), new FakeIThumbnailQuery()) + .LoopFolderAsync(metaFilesInDirectory, true); Assert.IsNotNull(metaFilesInDirectory); - - Assert.AreEqual(1,console.WrittenLines.Count); - Assert.AreEqual("πŸš€",console.WrittenLines[0]); + + Assert.AreEqual(1, console.WrittenLines.Count); + Assert.AreEqual("πŸš€", console.WrittenLines[0]); } [TestMethod] @@ -71,12 +71,13 @@ public async Task GeoLocationWriteLoopFolderTest_verbose() } }; var console = new FakeConsoleWrapper(); - await new GeoLocationWrite(new AppSettings{Verbose = true}, - _exifTool, new FakeSelectorStorage(),console, new FakeIWebLogger(), new FakeIThumbnailQuery()) - .LoopFolderAsync(metaFilesInDirectory, - true); + await new GeoLocationWrite(new AppSettings { Verbose = true }, + _exifTool, new FakeSelectorStorage(), console, new FakeIWebLogger(), + new FakeIThumbnailQuery()) + .LoopFolderAsync(metaFilesInDirectory, + true); - Assert.AreEqual(2,console.WrittenLines.Count); + Assert.AreEqual(2, console.WrittenLines.Count); Assert.IsTrue(console.WrittenLines.LastOrDefault()!.Contains("GeoLocationWrite")); } } diff --git a/starsky/starskytest/starsky.feature.import/Helpers/UpdateImportTransformationsTest.cs b/starsky/starskytest/starsky.feature.import/Helpers/UpdateImportTransformationsTest.cs index 8d891335db..f03356683d 100644 --- a/starsky/starskytest/starsky.feature.import/Helpers/UpdateImportTransformationsTest.cs +++ b/starsky/starskytest/starsky.feature.import/Helpers/UpdateImportTransformationsTest.cs @@ -8,7 +8,6 @@ using starsky.foundation.platform.Models; using starskytest.FakeCreateAn; using starskytest.FakeMocks; -using starskytest.Models; namespace starskytest.starsky.feature.import.Helpers { diff --git a/starsky/starskytest/starsky.feature.import/Services/ImportTest.cs b/starsky/starskytest/starsky.feature.import/Services/ImportTest.cs index 2f5af38a2e..62fad6aad1 100644 --- a/starsky/starskytest/starsky.feature.import/Services/ImportTest.cs +++ b/starsky/starskytest/starsky.feature.import/Services/ImportTest.cs @@ -18,7 +18,6 @@ using starsky.foundation.storage.Services; using starskytest.FakeCreateAn; using starskytest.FakeMocks; -using starskytest.Models; namespace starskytest.starsky.feature.import.Services { @@ -1155,7 +1154,7 @@ await importService.AddToQueryAndImportDatabaseAsync( Assert.AreEqual(0, logger.TrackedInformation.Count( p => p.Item2?.Contains("AddToQueryAndImportDatabaseAsync") == true)); } - + [TestMethod] public async Task RemoveFromQueryAndImportDatabaseAsync_NoConnection_NoVerbose() { diff --git a/starsky/starskytest/starsky.feature.import/Services/ImportTest_InMemoryDb.cs b/starsky/starskytest/starsky.feature.import/Services/ImportTest_InMemoryDb.cs index d76290180f..6265264a6f 100644 --- a/starsky/starskytest/starsky.feature.import/Services/ImportTest_InMemoryDb.cs +++ b/starsky/starskytest/starsky.feature.import/Services/ImportTest_InMemoryDb.cs @@ -17,7 +17,6 @@ using starsky.foundation.storage.Services; using starskytest.FakeCreateAn; using starskytest.FakeMocks; -using starskytest.Models; namespace starskytest.starsky.feature.import.Services { @@ -39,158 +38,166 @@ public ImportTestInMemoryDb() var provider = new ServiceCollection() .AddMemoryCache(); - _appSettings = new AppSettings{ - DatabaseType = AppSettings.DatabaseTypeList.InMemoryDatabase, - Verbose = true + _appSettings = new AppSettings + { + DatabaseType = AppSettings.DatabaseTypeList.InMemoryDatabase, Verbose = true }; provider.AddSingleton(_appSettings); new SetupDatabaseTypes(_appSettings, provider).BuilderDb(); - provider.AddScoped(); + provider.AddScoped(); provider.AddScoped(); provider.AddScoped(); provider.AddSingleton(); var serviceProvider = provider.BuildServiceProvider(); - + _query = serviceProvider.GetRequiredService(); _importQuery = serviceProvider.GetRequiredService(); _console = new ConsoleWrapper(); - + _iStorageFake = new FakeIStorage( - new List{"/"}, - new List{"/test.jpg","/color_class_winner.jpg"}, - new List{CreateAnImage.Bytes.ToArray(), CreateAnImageColorClass.Bytes.ToArray()} + new List { "/" }, + new List { "/test.jpg", "/color_class_winner.jpg" }, + new List + { + CreateAnImage.Bytes.ToArray(), CreateAnImageColorClass.Bytes.ToArray() + } ); - + _exampleHash = new FileHash(_iStorageFake).GetHashCode("/test.jpg").Key; } - + [TestMethod] public async Task Importer_Gpx() { var storage = new FakeIStorage( - new List{"/"}, - new List{"/test.gpx"}, - new List{CreateAnGpx.Bytes.ToArray()}); - - var importService = new Import(new FakeSelectorStorage(storage), _appSettings, new FakeIImportQuery(), - new FakeExifTool(storage, _appSettings),_query,_console, new FakeIMetaExifThumbnailService(), new FakeIWebLogger(),new FakeIThumbnailQuery(), new FakeMemoryCache()); - var expectedFilePath = await ImportTest.GetExpectedFilePathAsync(storage, _appSettings, "/test.gpx"); - - var result = await importService.Importer(new List {"/test.gpx"}, + new List { "/" }, + new List { "/test.gpx" }, + new List { CreateAnGpx.Bytes.ToArray() }); + + var importService = new Import(new FakeSelectorStorage(storage), _appSettings, + new FakeIImportQuery(), + new FakeExifTool(storage, _appSettings), _query, _console, + new FakeIMetaExifThumbnailService(), new FakeIWebLogger(), + new FakeIThumbnailQuery(), new FakeMemoryCache()); + var expectedFilePath = + await ImportTest.GetExpectedFilePathAsync(storage, _appSettings, "/test.gpx"); + + var result = await importService.Importer(new List { "/test.gpx" }, new ImportSettingsModel()); var getResult = await _query.GetObjectByFilePathAsync(expectedFilePath); Assert.IsNotNull(getResult); - Assert.AreEqual(expectedFilePath,getResult.FilePath); + Assert.AreEqual(expectedFilePath, getResult.FilePath); Assert.AreEqual(ImportStatus.Ok, result[0].Status); - + await _query.RemoveItemAsync(getResult); } - + [TestMethod] public async Task Importer_OverwriteStructure_HappyFlow() { - var importService = new Import(new FakeSelectorStorage(_iStorageFake), + var importService = new Import(new FakeSelectorStorage(_iStorageFake), _appSettings, new FakeIImportQuery(), - new FakeExifTool(_iStorageFake, _appSettings),_query, _console, - new FakeIMetaExifThumbnailService(), new FakeIWebLogger(),new FakeIThumbnailQuery(),new FakeMemoryCache()); - - var result = await importService.Importer(new List {"/test.jpg"}, - new ImportSettingsModel{ + new FakeExifTool(_iStorageFake, _appSettings), _query, _console, + new FakeIMetaExifThumbnailService(), new FakeIWebLogger(), + new FakeIThumbnailQuery(), new FakeMemoryCache()); + + var result = await importService.Importer(new List { "/test.jpg" }, + new ImportSettingsModel + { Structure = "/yyyy/MM/yyyy_MM_dd*/_yyyyMMdd_HHmmss.ext" }); - - var expectedFilePath = await ImportTest.GetExpectedFilePathAsync(_iStorageFake, new AppSettings - { - Structure = "/yyyy/MM/yyyy_MM_dd*/_yyyyMMdd_HHmmss.ext" - }, "/test.jpg"); - - Assert.AreEqual(expectedFilePath,result[0].FilePath); + + var expectedFilePath = await ImportTest.GetExpectedFilePathAsync(_iStorageFake, + new AppSettings { Structure = "/yyyy/MM/yyyy_MM_dd*/_yyyyMMdd_HHmmss.ext" }, + "/test.jpg"); + + Assert.AreEqual(expectedFilePath, result[0].FilePath); var queryResult = await _query.GetObjectByFilePathAsync(expectedFilePath); - + Assert.IsNotNull(queryResult); - Assert.AreEqual(expectedFilePath,queryResult.FilePath); + Assert.AreEqual(expectedFilePath, queryResult.FilePath); _iStorageFake.FileDelete(expectedFilePath); await _query.RemoveItemAsync(queryResult); } - + [TestMethod] public async Task Importer_HappyFlow_ItShouldAddTo_ImportDb() { - var importService = new Import(new FakeSelectorStorage(_iStorageFake), + var importService = new Import(new FakeSelectorStorage(_iStorageFake), _appSettings, _importQuery, - new FakeExifTool(_iStorageFake, _appSettings),_query, - _console, new FakeIMetaExifThumbnailService(), - new FakeIWebLogger(),new FakeIThumbnailQuery()); - - await importService.Importer(new List {"/test.jpg"}, - new ImportSettingsModel{ + new FakeExifTool(_iStorageFake, _appSettings), _query, + _console, new FakeIMetaExifThumbnailService(), + new FakeIWebLogger(), new FakeIThumbnailQuery()); + + await importService.Importer(new List { "/test.jpg" }, + new ImportSettingsModel + { Structure = "/yyyy/MM/yyyy_MM_dd*/_yyyyMMdd_HHmmss.ext" }); - + var isHashInImportDb = await _importQuery.IsHashInImportDbAsync(_exampleHash); Assert.IsTrue(isHashInImportDb); - - var expectedFilePath = await ImportTest.GetExpectedFilePathAsync(_iStorageFake, new AppSettings - { - Structure = "/yyyy/MM/yyyy_MM_dd*/_yyyyMMdd_HHmmss.ext" - }, "/test.jpg"); - + + var expectedFilePath = await ImportTest.GetExpectedFilePathAsync(_iStorageFake, + new AppSettings { Structure = "/yyyy/MM/yyyy_MM_dd*/_yyyyMMdd_HHmmss.ext" }, + "/test.jpg"); + var queryResult = await _query.GetObjectByFilePathAsync(expectedFilePath); Assert.IsNotNull(queryResult); _iStorageFake.FileDelete(expectedFilePath); await _query.RemoveItemAsync(queryResult); } - + [TestMethod] public async Task Importer_OverwriteColorClass() { - var importService = new Import(new FakeSelectorStorage(_iStorageFake), + var importService = new Import(new FakeSelectorStorage(_iStorageFake), _appSettings, new FakeIImportQuery(), new FakeExifTool(_iStorageFake, _appSettings), - _query, _console, new FakeIMetaExifThumbnailService(), + _query, _console, new FakeIMetaExifThumbnailService(), new FakeIWebLogger(), new FakeIThumbnailQuery()); - var expectedFilePath = await ImportTest.GetExpectedFilePathAsync(_iStorageFake, _appSettings, "/test.jpg"); - var result = await importService.Importer(new List {"/test.jpg"}, - new ImportSettingsModel{ - ColorClass = 5 - }); - + var expectedFilePath = + await ImportTest.GetExpectedFilePathAsync(_iStorageFake, _appSettings, "/test.jpg"); + var result = await importService.Importer(new List { "/test.jpg" }, + new ImportSettingsModel { ColorClass = 5 }); + Assert.IsNotNull(result.FirstOrDefault()); - Assert.AreEqual(expectedFilePath,result.FirstOrDefault()!.FilePath); + Assert.AreEqual(expectedFilePath, result.FirstOrDefault()!.FilePath); var queryResult = await _query.GetObjectByFilePathAsync(expectedFilePath); Assert.IsNotNull(queryResult); - Assert.AreEqual(expectedFilePath,queryResult.FilePath); - Assert.AreEqual(ColorClassParser.Color.Typical,queryResult.ColorClass); + Assert.AreEqual(expectedFilePath, queryResult.FilePath); + Assert.AreEqual(ColorClassParser.Color.Typical, queryResult.ColorClass); _iStorageFake.FileDelete(expectedFilePath); await _query.RemoveItemAsync(queryResult); } - + [TestMethod] public async Task Importer_ToDefaultFolderStructure_default_HappyFlow() { - var importService = new Import(new FakeSelectorStorage(_iStorageFake), + var importService = new Import(new FakeSelectorStorage(_iStorageFake), _appSettings, new FakeIImportQuery(), new FakeExifTool(_iStorageFake, _appSettings), - _query,_console, new FakeIMetaExifThumbnailService(), + _query, _console, new FakeIMetaExifThumbnailService(), new FakeIWebLogger(), new FakeIThumbnailQuery()); - var expectedFilePath = await ImportTest.GetExpectedFilePathAsync(_iStorageFake, _appSettings, "/test.jpg"); - var result = await importService.Importer(new List {"/test.jpg"}, + var expectedFilePath = + await ImportTest.GetExpectedFilePathAsync(_iStorageFake, _appSettings, "/test.jpg"); + var result = await importService.Importer(new List { "/test.jpg" }, new ImportSettingsModel()); - - Assert.AreEqual(expectedFilePath,result[0].FilePath); + + Assert.AreEqual(expectedFilePath, result[0].FilePath); var queryResult = await _query.GetObjectByFilePathAsync(expectedFilePath); Assert.IsNotNull(queryResult); - Assert.AreEqual(expectedFilePath,queryResult.FilePath); + Assert.AreEqual(expectedFilePath, queryResult.FilePath); _iStorageFake.FileDelete(expectedFilePath); await _query.RemoveItemAsync(queryResult); diff --git a/starsky/starskytest/starsky.feature.metaupdate/Services/MetaUpdateServiceTest.cs b/starsky/starskytest/starsky.feature.metaupdate/Services/MetaUpdateServiceTest.cs index 309687595e..21f023d77d 100644 --- a/starsky/starskytest/starsky.feature.metaupdate/Services/MetaUpdateServiceTest.cs +++ b/starsky/starskytest/starsky.feature.metaupdate/Services/MetaUpdateServiceTest.cs @@ -17,7 +17,6 @@ using starsky.foundation.storage.Storage; using starskytest.FakeCreateAn; using starskytest.FakeMocks; -using starskytest.Models; namespace starskytest.starsky.feature.metaupdate.Services { diff --git a/starsky/starskytest/starsky.feature.trash/Services/MoveToTrashServiceTest.cs b/starsky/starskytest/starsky.feature.trash/Services/MoveToTrashServiceTest.cs index d78e5798ce..19c7fac3bd 100644 --- a/starsky/starskytest/starsky.feature.trash/Services/MoveToTrashServiceTest.cs +++ b/starsky/starskytest/starsky.feature.trash/Services/MoveToTrashServiceTest.cs @@ -16,7 +16,6 @@ using starsky.foundation.platform.Models; using starsky.foundation.readmeta.Services; using starskytest.FakeMocks; -using starskytest.Models; namespace starskytest.starsky.feature.trash.Services; @@ -29,23 +28,23 @@ public async Task InSystemTrash_ShouldMoveToTrash() const string path = "/test/test.jpg"; var trashService = new FakeITrashService(); var appSettings = new AppSettings { UseSystemTrash = true }; - var moveToTrashService = new MoveToTrashService(appSettings, - new FakeIQuery(new List{new FileIndexItem(path) + var moveToTrashService = new MoveToTrashService(appSettings, + new FakeIQuery(new List { - Status = FileIndexItem.ExifStatus.Ok - }}), - new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), - trashService, new FakeIMetaUpdateService(), + new FileIndexItem(path) { Status = FileIndexItem.ExifStatus.Ok } + }), + new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), + trashService, new FakeIMetaUpdateService(), new FakeITrashConnectionService()); - await moveToTrashService.MoveToTrashAsync(new List{path}, true); - + await moveToTrashService.MoveToTrashAsync(new List { path }, true); + Assert.AreEqual(1, trashService.InTrash.Count); var expected = appSettings.StorageFolder + path.Replace('/', Path.DirectorySeparatorChar); Assert.AreEqual(expected, trashService.InTrash.FirstOrDefault()); } - + [TestMethod] public async Task InSystemTrash_ShouldMoveToTrash_Directory() { @@ -53,271 +52,278 @@ public async Task InSystemTrash_ShouldMoveToTrash_Directory() const string path = "/test/test.jpg"; var trashService = new FakeITrashService(); var appSettings = new AppSettings { UseSystemTrash = true }; - var moveToTrashService = new MoveToTrashService(appSettings, - new FakeIQuery(new List{ + var moveToTrashService = new MoveToTrashService(appSettings, + new FakeIQuery(new List + { new FileIndexItem(path) { - IsDirectory = false, - Status = FileIndexItem.ExifStatus.Ok + IsDirectory = false, Status = FileIndexItem.ExifStatus.Ok }, new FileIndexItem(dirPath) { - IsDirectory = true, - Status = FileIndexItem.ExifStatus.Ok + IsDirectory = true, Status = FileIndexItem.ExifStatus.Ok } - }), - new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), - trashService, new FakeIMetaUpdateService(), + }), + new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), + trashService, new FakeIMetaUpdateService(), new FakeITrashConnectionService()); - await moveToTrashService.MoveToTrashAsync(new List{dirPath}, true); - + await moveToTrashService.MoveToTrashAsync(new List { dirPath }, true); + Assert.AreEqual(1, trashService.InTrash.Count); var expected = appSettings.StorageFolder + dirPath.Replace('/', Path.DirectorySeparatorChar); Assert.AreEqual(expected, trashService.InTrash.FirstOrDefault()); } - + [TestMethod] public async Task InSystemTrash_ShouldMoveToTrash_Status() { const string path = "/test/test.jpg"; var trashService = new FakeITrashService(); var appSettings = new AppSettings { UseSystemTrash = true }; - var moveToTrashService = new MoveToTrashService(appSettings, - new FakeIQuery(new List{new FileIndexItem(path) + var moveToTrashService = new MoveToTrashService(appSettings, + new FakeIQuery(new List { - Status = FileIndexItem.ExifStatus.Ok - }}), - new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), - trashService, new FakeIMetaUpdateService(), + new FileIndexItem(path) { Status = FileIndexItem.ExifStatus.Ok } + }), + new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), + trashService, new FakeIMetaUpdateService(), new FakeITrashConnectionService()); var result = await moveToTrashService.MoveToTrashAsync( - new List{path}, true); - - Assert.AreEqual(FileIndexItem.ExifStatus.NotFoundSourceMissing, result.FirstOrDefault()?.Status); + new List { path }, true); + + Assert.AreEqual(FileIndexItem.ExifStatus.NotFoundSourceMissing, + result.FirstOrDefault()?.Status); } - + [TestMethod] public async Task InMetaTrash_Status() { const string path = "/test/test.jpg"; var trashService = new FakeITrashService(); var appSettings = new AppSettings { UseSystemTrash = false }; - var moveToTrashService = new MoveToTrashService(appSettings, - new FakeIQuery(new List{new FileIndexItem(path) + var moveToTrashService = new MoveToTrashService(appSettings, + new FakeIQuery(new List { - Status = FileIndexItem.ExifStatus.Ok - }}), - new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), - trashService, new FakeIMetaUpdateService(), + new FileIndexItem(path) { Status = FileIndexItem.ExifStatus.Ok } + }), + new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), + trashService, new FakeIMetaUpdateService(), new FakeITrashConnectionService()); var result = await moveToTrashService.MoveToTrashAsync( - new List{path}, true); - + new List { path }, true); + Assert.AreEqual(FileIndexItem.ExifStatus.Deleted, result.FirstOrDefault()?.Status); } - + [TestMethod] public async Task InMetaTrash_StatusOk_IsNotSupported_AndEnabled() { const string path = "/test/test.jpg"; - var trashService = new FakeITrashService(){IsSupported = false}; + var trashService = new FakeITrashService() { IsSupported = false }; var appSettings = new AppSettings { UseSystemTrash = true }; // see supported var metaUpdate = new FakeIMetaUpdateService(); - var moveToTrashService = new MoveToTrashService(appSettings, - new FakeIQuery(new List{new FileIndexItem(path) + var moveToTrashService = new MoveToTrashService(appSettings, + new FakeIQuery(new List { - Status = FileIndexItem.ExifStatus.Ok - }}), - new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), - trashService, metaUpdate, + new FileIndexItem(path) { Status = FileIndexItem.ExifStatus.Ok } + }), + new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), + trashService, metaUpdate, new FakeITrashConnectionService()); var result = await moveToTrashService.MoveToTrashAsync( - new List{path}, true); - + new List { path }, true); + Assert.AreEqual(0, trashService.InTrash.Count); Assert.AreEqual(TrashKeyword.TrashKeywordString, result.FirstOrDefault()?.Tags); } - + [TestMethod] public async Task InMetaTrash_StatusOk_IsSupported_AndDisabled() { const string path = "/test/test.jpg"; - var trashService = new FakeITrashService(){IsSupported = true}; - var appSettings = new AppSettings { UseSystemTrash = false }; // see supported and other test + var trashService = new FakeITrashService() { IsSupported = true }; + var appSettings = + new AppSettings { UseSystemTrash = false }; // see supported and other test var metaUpdate = new FakeIMetaUpdateService(); - var moveToTrashService = new MoveToTrashService(appSettings, - new FakeIQuery(new List{new FileIndexItem(path) + var moveToTrashService = new MoveToTrashService(appSettings, + new FakeIQuery(new List { - Status = FileIndexItem.ExifStatus.Ok - }}), - new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), - trashService, metaUpdate, + new FileIndexItem(path) { Status = FileIndexItem.ExifStatus.Ok } + }), + new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), + trashService, metaUpdate, new FakeITrashConnectionService()); var result = await moveToTrashService.MoveToTrashAsync( - new List{path}, true); - + new List { path }, true); + Assert.AreEqual(0, trashService.InTrash.Count); Assert.AreEqual(TrashKeyword.TrashKeywordString, result.FirstOrDefault()?.Tags); } - + [TestMethod] public async Task InMetaTrash_StatusDeleted() { const string path = "/test/test.jpg"; - var trashService = new FakeITrashService(){IsSupported = false}; + var trashService = new FakeITrashService() { IsSupported = false }; var appSettings = new AppSettings { UseSystemTrash = true }; // see supported var metaUpdate = new FakeIMetaUpdateService(); - var moveToTrashService = new MoveToTrashService(appSettings, - new FakeIQuery(new List{new FileIndexItem(path) + var moveToTrashService = new MoveToTrashService(appSettings, + new FakeIQuery(new List { - Status = FileIndexItem.ExifStatus.Deleted - }}), - new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), - trashService, metaUpdate, + new FileIndexItem(path) { Status = FileIndexItem.ExifStatus.Deleted } + }), + new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), + trashService, metaUpdate, new FakeITrashConnectionService()); var result = await moveToTrashService.MoveToTrashAsync( - new List{path}, true); - + new List { path }, true); + Assert.AreEqual(0, trashService.InTrash.Count); Assert.AreEqual(TrashKeyword.TrashKeywordString, result.FirstOrDefault()?.Tags); } - + [TestMethod] public async Task InMetaTrash_WithDbContext() { const string path = "/test/test.jpg"; - - var trashService = new FakeITrashService(){IsSupported = false}; + + var trashService = new FakeITrashService() { IsSupported = false }; var appSettings = new AppSettings { - UseSystemTrash = false, - DatabaseType = AppSettings.DatabaseTypeList.InMemoryDatabase + UseSystemTrash = false, DatabaseType = AppSettings.DatabaseTypeList.InMemoryDatabase }; // see supported - + var builderDb = new DbContextOptionsBuilder(); builderDb.UseInMemoryDatabase(nameof(MoveToTrashServiceTest)); var options = builderDb.Options; var dbContext = new ApplicationDbContext(options); - var serviceCollection = new ServiceCollection().AddScoped(_ => new ApplicationDbContext(options)); - var serviceScopeFactory = serviceCollection.BuildServiceProvider().GetService(); - + var serviceCollection = + new ServiceCollection().AddScoped(_ => new ApplicationDbContext(options)); + var serviceScopeFactory = + serviceCollection.BuildServiceProvider().GetService(); + var storage = new FakeIStorage( - new List{"/", "/test"}, - new List{path} + new List { "/", "/test" }, + new List { path } ); var query = new Query(dbContext, appSettings, serviceScopeFactory, new FakeIWebLogger()); - var addedItem = await query.AddItemAsync(new FileIndexItem(path){Id = 9000}); - - var metaUpdate = new MetaUpdateService(query, new FakeExifTool(storage, appSettings), - new FakeSelectorStorage(storage), new MetaPreflight(query, appSettings, new FakeSelectorStorage(storage), - new FakeIWebLogger()), new FakeIWebLogger(), new ReadMetaSubPathStorage(new FakeSelectorStorage(storage), - appSettings, null!, new FakeIWebLogger()), new FakeIThumbnailService(), - new ThumbnailQuery(dbContext,null, new FakeIWebLogger())); + var addedItem = await query.AddItemAsync(new FileIndexItem(path) { Id = 9000 }); + + var metaUpdate = new MetaUpdateService(query, new FakeExifTool(storage, appSettings), + new FakeSelectorStorage(storage), new MetaPreflight(query, appSettings, + new FakeSelectorStorage(storage), + new FakeIWebLogger()), new FakeIWebLogger(), new ReadMetaSubPathStorage( + new FakeSelectorStorage(storage), + appSettings, null!, new FakeIWebLogger()), new FakeIThumbnailService(), + new ThumbnailQuery(dbContext, null, new FakeIWebLogger())); var metaPreflight = new MetaPreflight(query, appSettings, new FakeSelectorStorage(storage), new FakeIWebLogger()); - + var moveToTrashService = new MoveToTrashService(appSettings, query, metaPreflight, new FakeIUpdateBackgroundTaskQueue(), new TrashService(), metaUpdate, new FakeITrashConnectionService()); var result = await moveToTrashService.MoveToTrashAsync( - new List{path}, true); + new List { path }, true); await query.RemoveItemAsync(addedItem); - + Assert.AreEqual(0, trashService.InTrash.Count); Assert.AreEqual(TrashKeyword.TrashKeywordString, result.FirstOrDefault()?.Tags); } - + [TestMethod] public async Task InMetaTrash_WithDbContext_Directory() { const string path = "/test"; const string childItem = "/test/test.jpg"; - - var trashService = new FakeITrashService(){IsSupported = false}; + + var trashService = new FakeITrashService() { IsSupported = false }; var appSettings = new AppSettings { - UseSystemTrash = false, - DatabaseType = AppSettings.DatabaseTypeList.InMemoryDatabase + UseSystemTrash = false, DatabaseType = AppSettings.DatabaseTypeList.InMemoryDatabase }; // see supported - + var builderDb = new DbContextOptionsBuilder(); builderDb.UseInMemoryDatabase(nameof(MoveToTrashServiceTest)); var options = builderDb.Options; var dbContext = new ApplicationDbContext(options); - var serviceCollection = new ServiceCollection().AddScoped(_ => new ApplicationDbContext(options)); - var serviceScopeFactory = serviceCollection.BuildServiceProvider().GetService(); - + var serviceCollection = + new ServiceCollection().AddScoped(_ => new ApplicationDbContext(options)); + var serviceScopeFactory = + serviceCollection.BuildServiceProvider().GetService(); + var storage = new FakeIStorage( - new List{"/", "/test"}, - new List{path} + new List { "/", "/test" }, + new List { path } ); var query = new Query(dbContext, appSettings, serviceScopeFactory, new FakeIWebLogger()); var addedItem = await query.AddRangeAsync(new List { - new FileIndexItem(path){Id = 8830, IsDirectory = true}, - new FileIndexItem(childItem){Id = 8831} + new FileIndexItem(path) { Id = 8830, IsDirectory = true }, + new FileIndexItem(childItem) { Id = 8831 } }); Console.WriteLine("add done"); - - var metaUpdate = new MetaUpdateService(query, new FakeExifTool(storage, appSettings), - new FakeSelectorStorage(storage), new MetaPreflight(query, appSettings, new FakeSelectorStorage(storage), - new FakeIWebLogger()), new FakeIWebLogger(), new ReadMetaSubPathStorage(new FakeSelectorStorage(storage), - appSettings, null!, new FakeIWebLogger()), new FakeIThumbnailService(), - new ThumbnailQuery(dbContext,null, new FakeIWebLogger())); + + var metaUpdate = new MetaUpdateService(query, new FakeExifTool(storage, appSettings), + new FakeSelectorStorage(storage), new MetaPreflight(query, appSettings, + new FakeSelectorStorage(storage), + new FakeIWebLogger()), new FakeIWebLogger(), new ReadMetaSubPathStorage( + new FakeSelectorStorage(storage), + appSettings, null!, new FakeIWebLogger()), new FakeIThumbnailService(), + new ThumbnailQuery(dbContext, null, new FakeIWebLogger())); var metaPreflight = new MetaPreflight(query, appSettings, new FakeSelectorStorage(storage), new FakeIWebLogger()); - + var moveToTrashService = new MoveToTrashService(appSettings, query, metaPreflight, new FakeIUpdateBackgroundTaskQueue(), new TrashService(), metaUpdate, new FakeITrashConnectionService()); var result = await moveToTrashService.MoveToTrashAsync( - new List{path}, true); + new List { path }, true); await query.RemoveItemAsync(addedItem); - + // not in system trash Assert.AreEqual(0, trashService.InTrash.Count); - + // result Assert.AreEqual(2, result.Count); Assert.AreEqual(TrashKeyword.TrashKeywordString, result[0].Tags); Assert.AreEqual(TrashKeyword.TrashKeywordString, result[1].Tags); } - + [TestMethod] public void DetectToUseSystemTrash_False() { - var trashService = new FakeITrashService(){IsSupported = false}; - var moveToTrashService = new MoveToTrashService(new AppSettings(), new FakeIQuery(), - new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), - trashService, new FakeIMetaUpdateService(), + var trashService = new FakeITrashService() { IsSupported = false }; + var moveToTrashService = new MoveToTrashService(new AppSettings(), new FakeIQuery(), + new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), + trashService, new FakeIMetaUpdateService(), new FakeITrashConnectionService()); - var result = moveToTrashService.DetectToUseSystemTrash(); - + var result = moveToTrashService.DetectToUseSystemTrash(); + Assert.AreEqual(false, result); } @@ -325,23 +331,21 @@ public void DetectToUseSystemTrash_False() public async Task AppendChildItemsToTrashList_NoAny() { const string path = "/test/test.jpg"; - var trashService = new FakeITrashService(){IsSupported = false}; + var trashService = new FakeITrashService() { IsSupported = false }; var appSettings = new AppSettings { UseSystemTrash = true }; // see supported var metaUpdate = new FakeIMetaUpdateService(); - var moveToTrashService = new MoveToTrashService(appSettings, - new FakeIQuery(new List{new FileIndexItem(path) + var moveToTrashService = new MoveToTrashService(appSettings, + new FakeIQuery(new List { - Status = FileIndexItem.ExifStatus.Deleted - }}), - new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), - trashService, metaUpdate, + new FileIndexItem(path) { Status = FileIndexItem.ExifStatus.Deleted } + }), + new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), + trashService, metaUpdate, new FakeITrashConnectionService()); var (fileIndexResultsList, _) = await moveToTrashService.AppendChildItemsToTrashList( - new List - { - new FileIndexItem("") - }, new Dictionary>()); + new List { new FileIndexItem("") }, + new Dictionary>()); Assert.AreEqual(FileIndexItem.ExifStatus.Default, fileIndexResultsList.FirstOrDefault()?.Status); diff --git a/starsky/starskytest/starsky.feature.webhtmlpublish/Services/WebHtmlPublishServiceTest.cs b/starsky/starskytest/starsky.feature.webhtmlpublish/Services/WebHtmlPublishServiceTest.cs index 53f6bdd349..4b66ded3a5 100644 --- a/starsky/starskytest/starsky.feature.webhtmlpublish/Services/WebHtmlPublishServiceTest.cs +++ b/starsky/starskytest/starsky.feature.webhtmlpublish/Services/WebHtmlPublishServiceTest.cs @@ -11,7 +11,6 @@ using starsky.foundation.storage.Storage; using starskytest.FakeCreateAn; using starskytest.FakeMocks; -using starskytest.Models; namespace starskytest.starsky.feature.webhtmlpublish.Services { @@ -202,7 +201,8 @@ public async Task PreGenerateThumbnail_Test() new List { CreateAnImageNoExif.Bytes.ToArray() }); var selectorStorage = new FakeSelectorStorage(storage); - var service = new WebHtmlPublishService(new FakeIPublishPreflight(), selectorStorage, null!, + var service = new WebHtmlPublishService(new FakeIPublishPreflight(), selectorStorage, + null!, null!, null!, null!, new FakeIWebLogger(), new FakeIThumbnailService(selectorStorage)); var input = new List diff --git a/starsky/starskytest/Interfaces/IUserManagerTest.cs b/starsky/starskytest/starsky.foundation.accountmanagement/Interfaces/IUserManagerTest.cs similarity index 52% rename from starsky/starskytest/Interfaces/IUserManagerTest.cs rename to starsky/starskytest/starsky.foundation.accountmanagement/Interfaces/IUserManagerTest.cs index ed5e849444..8b9f6ab5ef 100644 --- a/starsky/starskytest/Interfaces/IUserManagerTest.cs +++ b/starsky/starskytest/starsky.foundation.accountmanagement/Interfaces/IUserManagerTest.cs @@ -2,7 +2,7 @@ using starsky.foundation.accountmanagement.Interfaces; using starsky.foundation.database.Models.Account; -namespace starskytest.Interfaces +namespace starskytest.starsky.foundation.accountmanagement.Interfaces { [TestClass] public sealed class UserManagerTest @@ -11,37 +11,38 @@ public sealed class UserManagerTest public void UserManagerTestSuccessFalse() { var error = new ChangeSecretResultError(); - var secretResult = new ChangeSecretResult(false,error); - Assert.AreEqual(false,secretResult.Success); + var secretResult = new ChangeSecretResult(false, error); + Assert.AreEqual(false, secretResult.Success); } - + [TestMethod] public void UserManagerTestChangeSecretResult() { var error = new ChangeSecretResultError(); - var secretResult = new ChangeSecretResult(false,error); - Assert.AreEqual(error,secretResult.Error); + var secretResult = new ChangeSecretResult(false, error); + Assert.AreEqual(error, secretResult.Error); } [TestMethod] public void UserManagerTestSignUpResult() { - var result = new SignUpResult(new User{Name = "test"}); - Assert.AreEqual("test",result.User?.Name); + var result = new SignUpResult(new User { Name = "test" }); + Assert.AreEqual("test", result.User?.Name); } - + [TestMethod] public void UserManagerTestSignUpResultFalse() { - var result = new SignUpResult(new User{Name = "test"},false, new SignUpResultError()); + var result = + new SignUpResult(new User { Name = "test" }, false, new SignUpResultError()); Assert.IsFalse(result.Success); } - + [TestMethod] public void UserManagerTestSignUpResultError() { - var result = new SignUpResult(null,false, new SignUpResultError()); - Assert.AreEqual(new SignUpResultError(),result.Error); + var result = new SignUpResult(null, false, new SignUpResultError()); + Assert.AreEqual(new SignUpResultError(), result.Error); } } } diff --git a/starsky/starskytest/Extensions/EntityFrameworkExtensionsTest.cs b/starsky/starskytest/starsky.foundation.database/Extensions/EntityFrameworkExtensionsTest.cs similarity index 77% rename from starsky/starskytest/Extensions/EntityFrameworkExtensionsTest.cs rename to starsky/starskytest/starsky.foundation.database/Extensions/EntityFrameworkExtensionsTest.cs index 30fafc4e74..6483e7ebc6 100644 --- a/starsky/starskytest/Extensions/EntityFrameworkExtensionsTest.cs +++ b/starsky/starskytest/starsky.foundation.database/Extensions/EntityFrameworkExtensionsTest.cs @@ -12,10 +12,10 @@ using starsky.foundation.database.Extensions; using starskytest.FakeMocks; -namespace starskytest.Extensions +namespace starskytest.starsky.foundation.database.Extensions { [TestClass] - public sealed class TestConnectionTest + public sealed class EntityFrameworkExtensionsTest { [TestMethod] public void TestConnection_Default() @@ -23,71 +23,71 @@ public void TestConnection_Default() var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; - + var context = new ApplicationDbContext(options); - Assert.AreEqual(true,context.TestConnection(new FakeIWebLogger())); + Assert.AreEqual(true, context.TestConnection(new FakeIWebLogger())); } - + [TestMethod] public void TestConnection_Mysql_Default() { var options = new DbContextOptionsBuilder() - .UseMySql("Server=localhost;Port=1234;database=test;uid=test;pwd=test;", - ServerVersion.Create(5, 0, 0,ServerType.MariaDb)) + .UseMySql("Server=localhost;Port=1234;database=test;uid=test;pwd=test;", + ServerVersion.Create(5, 0, 0, ServerType.MariaDb)) .Options; - + var context = new ApplicationDbContext(options); - Assert.AreEqual(true,context.TestConnection(new FakeIWebLogger())); + Assert.AreEqual(true, context.TestConnection(new FakeIWebLogger())); } - + [TestMethod] public void TestConnection_Cache_ShouldSetAfterWards() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; - + var context = new ApplicationDbContext(options); var provider = new ServiceCollection() .AddMemoryCache() .BuildServiceProvider(); var memoryCache = provider.GetRequiredService(); - + var result = context.TestConnection(new FakeIWebLogger(), memoryCache); - Assert.AreEqual(true,result); - + Assert.AreEqual(true, result); + memoryCache.TryGetValue("TestConnection", out var result2); Assert.AreEqual(true, result2); } - + [TestMethod] public void TestConnection_Cache_ShouldGetBefore() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; - + var context = new ApplicationDbContext(options); var provider = new ServiceCollection() .AddMemoryCache() .BuildServiceProvider(); var memoryCache = provider.GetRequiredService(); memoryCache.Set("TestConnection", false); - + var result = context.TestConnection(new FakeIWebLogger(), memoryCache); - Assert.AreEqual(false,result); - + Assert.AreEqual(false, result); + memoryCache.TryGetValue("TestConnection", out var result2); Assert.AreEqual(false, result2); } - - + + private class AppDbMySqlException : ApplicationDbContext { public AppDbMySqlException(DbContextOptions options) : base(options) { } - + private static MySqlException CreateMySqlException(string message) { // MySqlErrorCode errorCode, string? sqlState, string message, Exception? innerException @@ -96,38 +96,38 @@ private static MySqlException CreateMySqlException(string message) typeof(MySqlException).GetConstructors( BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod); - var ctor = ctorLIst.FirstOrDefault(p => - p.ToString() == "Void .ctor(MySqlConnector.MySqlErrorCode, System.String, System.String, System.Exception)" ); - + var ctor = ctorLIst.FirstOrDefault(p => + p.ToString() == + "Void .ctor(MySqlConnector.MySqlErrorCode, System.String, System.String, System.Exception)"); + var instance = - ( MySqlException? ) ctor?.Invoke(new object[] + ( MySqlException? )ctor?.Invoke(new object[] { - MySqlErrorCode.AccessDenied, - "test", - message, - new Exception() + MySqlErrorCode.AccessDenied, "test", message, new Exception() }); return instance!; } - - public override DatabaseFacade Database => throw CreateMySqlException("Database is not available"); + + public override DatabaseFacade Database => + throw CreateMySqlException("Database is not available"); } - - + + [TestMethod] public void TestConnection_MySqlException() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; - + var context = new AppDbMySqlException(options); var logger = new FakeIWebLogger(); var result = context.TestConnection(logger); - - Assert.AreEqual(false,result); - Assert.IsTrue(logger.TrackedInformation.FirstOrDefault().Item2?.Contains("Database is not available")); + + Assert.AreEqual(false, result); + Assert.IsTrue(logger.TrackedInformation.FirstOrDefault().Item2 + ?.Contains("Database is not available")); } - } + } } diff --git a/starsky/starskytest/Helpers/BreadcrumbHelperTest.cs b/starsky/starskytest/starsky.foundation.database/Helpers/BreadcrumbHelperTest.cs similarity index 94% rename from starsky/starskytest/Helpers/BreadcrumbHelperTest.cs rename to starsky/starskytest/starsky.foundation.database/Helpers/BreadcrumbHelperTest.cs index 29db2620bf..6bbbe7598a 100644 --- a/starsky/starskytest/Helpers/BreadcrumbHelperTest.cs +++ b/starsky/starskytest/starsky.foundation.database/Helpers/BreadcrumbHelperTest.cs @@ -2,11 +2,10 @@ using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.foundation.database.Helpers; -using starskycore.Attributes; +using starsky.project.web.Attributes; -namespace starskytest.Helpers +namespace starskytest.starsky.foundation.database.Helpers { - /// /// Also known as BreadcrumbsTest /// @@ -22,7 +21,7 @@ public void BreadcrumbSlashMethodTest() var breadcrumblist = new List { "/" }; CollectionAssert.AreEqual(breadcrumbExample, breadcrumblist); } - + [TestMethod] public void BreadcrumbNoInputTest() { diff --git a/starsky/starskytest/Models/Account/CredentialTest.cs b/starsky/starskytest/starsky.foundation.database/Models/Account/CredentialTest.cs similarity index 63% rename from starsky/starskytest/Models/Account/CredentialTest.cs rename to starsky/starskytest/starsky.foundation.database/Models/Account/CredentialTest.cs index c18c8adb18..5e1e442967 100644 --- a/starsky/starskytest/Models/Account/CredentialTest.cs +++ b/starsky/starskytest/starsky.foundation.database/Models/Account/CredentialTest.cs @@ -1,7 +1,7 @@ ο»Ώusing Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.foundation.database.Models.Account; -namespace starskytest.Models.Account +namespace starskytest.starsky.foundation.database.Models.Account { [TestClass] public sealed class CredentialTest @@ -9,7 +9,6 @@ public sealed class CredentialTest [TestMethod] public void CredentialSetupTest() { - // public int Id // public int UserId // public int CredentialTypeId @@ -19,7 +18,7 @@ public void CredentialSetupTest() // public User User // public CredentialType CredentialType - var creds = new Credential + var credential = new Credential { Id = 0, UserId = 0, @@ -30,14 +29,13 @@ public void CredentialSetupTest() User = new User(), CredentialType = new CredentialType() }; - - Assert.AreEqual(0, creds.Id); - Assert.AreEqual(0, creds.UserId); - Assert.AreEqual(0, creds.CredentialTypeId); - Assert.AreEqual(string.Empty, creds.Identifier); - Assert.AreEqual( new User().Id, creds.User.Id); - Assert.AreEqual( new CredentialType().Code, creds.CredentialType.Code); + Assert.AreEqual(0, credential.Id); + Assert.AreEqual(0, credential.UserId); + Assert.AreEqual(0, credential.CredentialTypeId); + Assert.AreEqual(string.Empty, credential.Identifier); + Assert.AreEqual(new User().Id, credential.User.Id); + Assert.AreEqual(new CredentialType().Code, credential.CredentialType.Code); } } } diff --git a/starsky/starskytest/starsky.foundation.database/Models/Account/CredentialTypeTest.cs b/starsky/starskytest/starsky.foundation.database/Models/Account/CredentialTypeTest.cs index 2a6e136bd0..507452fefd 100644 --- a/starsky/starskytest/starsky.foundation.database/Models/Account/CredentialTypeTest.cs +++ b/starsky/starskytest/starsky.foundation.database/Models/Account/CredentialTypeTest.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.foundation.database.Models.Account; @@ -10,9 +11,25 @@ public sealed class CredentialTypeTest public void CredentialType_Name_Credentials() { var rolePermission = new CredentialType(); - + Assert.IsNull(rolePermission.Name); Assert.IsNull(rolePermission.Credentials); } + + [TestMethod] + public void CredentialTypeSetup_Test() + { + var credentialType = new CredentialType + { + Id = 0, + Code = string.Empty, + Name = string.Empty, + Position = 0, + Credentials = new List() + }; + Assert.AreEqual(0, credentialType.Id); + Assert.AreEqual(0, credentialType.Position); + Assert.AreEqual(string.Empty, credentialType.Code); + } } } diff --git a/starsky/starskytest/starsky.foundation.database/Models/Account/PermissionTest.cs b/starsky/starskytest/starsky.foundation.database/Models/Account/PermissionTest.cs new file mode 100644 index 0000000000..ba678de8f9 --- /dev/null +++ b/starsky/starskytest/starsky.foundation.database/Models/Account/PermissionTest.cs @@ -0,0 +1,21 @@ +ο»Ώusing Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.database.Models.Account; + +namespace starskytest.starsky.foundation.database.Models.Account +{ + [TestClass] + public sealed class PermissionTest + { + [TestMethod] + public void CredentialSetupTest() + { + var permission = new Permission + { + Id = 0, Code = string.Empty, Name = string.Empty, Position = 0 + }; + Assert.AreEqual(0, permission.Id); + Assert.AreEqual(0, permission.Position); + Assert.AreEqual(string.Empty, permission.Code); + } + } +} diff --git a/starsky/starskytest/starsky.foundation.database/Models/Account/RolePermissionTest.cs b/starsky/starskytest/starsky.foundation.database/Models/Account/RolePermissionTest.cs index 712b5e89c7..d858c45972 100644 --- a/starsky/starskytest/starsky.foundation.database/Models/Account/RolePermissionTest.cs +++ b/starsky/starskytest/starsky.foundation.database/Models/Account/RolePermissionTest.cs @@ -10,9 +10,21 @@ public sealed class RolePermissionTest public void RolePermission_Role_Permission() { var rolePermission = new RolePermission(); - + Assert.IsNull(rolePermission.Role); Assert.IsNull(rolePermission.Permission); } + + [TestMethod] + public void RolePermissionSetupTest() + { + // RoleId + PermissionId + var rolePermission = new RolePermission + { + RoleId = 0, PermissionId = 0, Role = new Role(), Permission = new Permission() + }; + Assert.AreEqual(0, rolePermission.RoleId); + Assert.AreEqual(0, rolePermission.PermissionId); + } } } diff --git a/starsky/starskytest/Models/Account/RoleTest.cs b/starsky/starskytest/starsky.foundation.database/Models/Account/RoleTest.cs similarity index 66% rename from starsky/starskytest/Models/Account/RoleTest.cs rename to starsky/starskytest/starsky.foundation.database/Models/Account/RoleTest.cs index cc9c3884f4..c7efae7812 100644 --- a/starsky/starskytest/Models/Account/RoleTest.cs +++ b/starsky/starskytest/starsky.foundation.database/Models/Account/RoleTest.cs @@ -1,7 +1,7 @@ ο»Ώusing Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.foundation.database.Models.Account; -namespace starskytest.Models.Account +namespace starskytest.starsky.foundation.database.Models.Account { [TestClass] public sealed class RoleTest @@ -9,14 +9,7 @@ public sealed class RoleTest [TestMethod] public void RoleSetupTest() { - - var role = new Role - { - Id = 0, - Code = string.Empty, - Name = string.Empty, - Position = 0 - }; + var role = new Role { Id = 0, Code = string.Empty, Name = string.Empty, Position = 0 }; Assert.AreEqual(0, role.Id); Assert.AreEqual(0, role.Position); Assert.AreEqual(string.Empty, role.Code); diff --git a/starsky/starskytest/starsky.foundation.database/Models/Account/UserRoleTest.cs b/starsky/starskytest/starsky.foundation.database/Models/Account/UserRoleTest.cs index ec94c9ae2a..7eae001f77 100644 --- a/starsky/starskytest/starsky.foundation.database/Models/Account/UserRoleTest.cs +++ b/starsky/starskytest/starsky.foundation.database/Models/Account/UserRoleTest.cs @@ -10,9 +10,20 @@ public sealed class UserRoleTest public void UserRole_User_Role() { var rolePermission = new UserRole(); - + Assert.IsNull(rolePermission.Role); Assert.IsNull(rolePermission.User); } + + [TestMethod] + public void UserRoleTest_SetupTest() + { + var role = new UserRole() + { + UserId = 0, RoleId = 0, User = new User(), Role = new Role() + }; + Assert.AreEqual(0, role.UserId); + Assert.AreEqual(0, role.RoleId); + } } } diff --git a/starsky/starskytest/Models/Account/UserTest.cs b/starsky/starskytest/starsky.foundation.database/Models/Account/UserTest.cs similarity index 89% rename from starsky/starskytest/Models/Account/UserTest.cs rename to starsky/starskytest/starsky.foundation.database/Models/Account/UserTest.cs index 7ff1450d61..0674a1e61e 100644 --- a/starsky/starskytest/Models/Account/UserTest.cs +++ b/starsky/starskytest/starsky.foundation.database/Models/Account/UserTest.cs @@ -3,7 +3,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.foundation.database.Models.Account; -namespace starskytest.Models.Account +namespace starskytest.starsky.foundation.database.Models.Account { [TestClass] public sealed class UserTest diff --git a/starsky/starskytest/starsky.foundation.database/Models/FileIndexItemTest.cs b/starsky/starskytest/starsky.foundation.database/Models/FileIndexItemTest.cs index d9d0a8f417..04eb04a079 100644 --- a/starsky/starskytest/starsky.foundation.database/Models/FileIndexItemTest.cs +++ b/starsky/starskytest/starsky.foundation.database/Models/FileIndexItemTest.cs @@ -5,6 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.foundation.database.Models; using starsky.foundation.platform.Helpers; +using starsky.foundation.writemeta.Helpers; namespace starskytest.starsky.foundation.database.Models { @@ -14,135 +15,141 @@ public sealed class FileIndexItemTest [TestMethod] public void FileIndexItemTest_SetTagsToNull() { - var item = new FileIndexItem{Tags = null}; - Assert.AreEqual(item.Tags,string.Empty); + var item = new FileIndexItem { Tags = null }; + Assert.AreEqual(item.Tags, string.Empty); } - + [TestMethod] public void FileIndexItemTest_KeywordsToNull() { - var item = new FileIndexItem{Keywords = null}; + var item = new FileIndexItem { Keywords = null }; // > read tags instead of keywords - Assert.AreEqual(item.Tags,string.Empty); + Assert.AreEqual(item.Tags, string.Empty); } [TestMethod] public void FileIndexItem_DoubleSpaces() { - var item = new FileIndexItem{Tags = "test0, test1Β  ,Β Β  test2,Β Β  test3, " + - "test4,Β Β  test5, test6, test7,Β Β  "}; - + var item = new FileIndexItem + { + Tags = "test0, test1Β  ,Β Β  test2,Β Β  test3, " + + "test4,Β Β  test5, test6, test7,Β Β  " + }; + Assert.AreEqual("test1", item.Keywords?.ToList()[1]); Assert.AreEqual("test2", item.Keywords?.ToList()[2]); Assert.AreEqual("test5", item.Keywords?.ToList()[5]); Assert.AreEqual("test7", item.Keywords?.ToList()[7]); - Assert.AreEqual(8,item.Keywords?.Count); + Assert.AreEqual(8, item.Keywords?.Count); } - + [TestMethod] public void FileIndexItemTest_SetDescriptionsToNull() { - var item = new FileIndexItem{Description = null}; - Assert.AreEqual(item.Description,string.Empty); + var item = new FileIndexItem { Description = null }; + Assert.AreEqual(item.Description, string.Empty); } - - + + [TestMethod] public void FileIndexItemTest_SetColorClassTestDefault() { var input = ColorClassParser.GetColorClass(); var output = ColorClassParser.Color.None; - Assert.AreEqual(input,output); + Assert.AreEqual(input, output); } - + [TestMethod] public void FileIndexItemTest_SetColorClassTestMin1() { var input = ColorClassParser.GetColorClass("-1"); var output = ColorClassParser.Color.DoNotChange; - Assert.AreEqual(input,output); + Assert.AreEqual(input, output); } - + [TestMethod] [SuppressMessage("ReSharper", "RedundantArgumentDefaultValue")] public void FileIndexItemTest_SetColorClassTest0() { var input = ColorClassParser.GetColorClass("0"); var output = ColorClassParser.Color.None; - Assert.AreEqual(input,output); + Assert.AreEqual(input, output); } - + [TestMethod] public void FileIndexItemTest_SetColorClassTest1() { var input = ColorClassParser.GetColorClass("1"); var output = ColorClassParser.Color.Winner; - Assert.AreEqual(input,output); + Assert.AreEqual(input, output); } - + [TestMethod] public void FileIndexItemTest_SetColorClassTest2() { var input = ColorClassParser.GetColorClass("2"); var output = ColorClassParser.Color.WinnerAlt; - Assert.AreEqual(input,output); + Assert.AreEqual(input, output); } - + [TestMethod] public void FileIndexItemTest_SetColorClassTest3() { var input = ColorClassParser.GetColorClass("3"); var output = ColorClassParser.Color.Superior; - Assert.AreEqual(input,output); + Assert.AreEqual(input, output); } - + [TestMethod] public void FileIndexItemTest_SetColorClassTest4() { var input = ColorClassParser.GetColorClass("4"); var output = ColorClassParser.Color.SuperiorAlt; - Assert.AreEqual(input,output); + Assert.AreEqual(input, output); } - + [TestMethod] public void FileIndexItemTest_SetColorClassTest5() { var input = ColorClassParser.GetColorClass("5"); var output = ColorClassParser.Color.Typical; - Assert.AreEqual(input,output); + Assert.AreEqual(input, output); } - + [TestMethod] public void FileIndexItemTest_SetColorClassTest6() { var input = ColorClassParser.GetColorClass("6"); var output = ColorClassParser.Color.TypicalAlt; - Assert.AreEqual(input,output); + Assert.AreEqual(input, output); } - + [TestMethod] public void FileIndexItemTest_SetColorClassTest7() { var input = ColorClassParser.GetColorClass("7"); var output = ColorClassParser.Color.Extras; - Assert.AreEqual(input,output); + Assert.AreEqual(input, output); } - + [TestMethod] public void FileIndexItemTest_SetColorClassTest8() { var input = ColorClassParser.GetColorClass("8"); var output = ColorClassParser.Color.Trash; - Assert.AreEqual(input,output); + Assert.AreEqual(input, output); } [TestMethod] public void FileIndexItemTest_GetColorClassListTestEightSeven() { var input = "8,7"; - var eightSeven = new List {ColorClassParser.Color.Trash,ColorClassParser.Color.Extras}; + var eightSeven = new List + { + ColorClassParser.Color.Trash, ColorClassParser.Color.Extras + }; var output = FileIndexItem.GetColorClassList(input); - CollectionAssert.AreEqual(eightSeven,output); + CollectionAssert.AreEqual(eightSeven, output); } [TestMethod] @@ -150,80 +157,80 @@ public void FileIndexItemTest_GetColorClassListString() { var input = "string"; var output = FileIndexItem.GetColorClassList(input); - Assert.AreEqual(0,output.Count); // <= 0 + Assert.AreEqual(0, output.Count); // <= 0 } [TestMethod] public void FileIndexItemTest_FileIndexItemTitleTest() { - var fileIndexItem = new FileIndexItem {Title = null}; - Assert.AreEqual(fileIndexItem.Title,string.Empty); + var fileIndexItem = new FileIndexItem { Title = null }; + Assert.AreEqual(fileIndexItem.Title, string.Empty); } [TestMethod] public void FileIndexItemTest_FileNameNull() { var t = new FileIndexItem(); - Assert.AreEqual(string.Empty,t.FileName); + Assert.AreEqual(string.Empty, t.FileName); } - + [TestMethod] public void FileIndexItemTest_ParentDirectoryNull() { var t = new FileIndexItem(); - Assert.AreEqual(string.Empty,t.ParentDirectory); + Assert.AreEqual(string.Empty, t.ParentDirectory); } - + [TestMethod] public void FileIndexItemTest_FilePathNull() { var t = new FileIndexItem(); - Assert.AreEqual("/",t.FilePath); + Assert.AreEqual("/", t.FilePath); } - + [TestMethod] public void FileIndexItemTest_OrientationrelativeRotation0() { // keep the same - var t = new FileIndexItem {Orientation = FileIndexItem.Rotation.Horizontal}; - Assert.AreEqual(FileIndexItem.Rotation.Horizontal,t.RelativeOrientation()); + var t = new FileIndexItem { Orientation = FileIndexItem.Rotation.Horizontal }; + Assert.AreEqual(FileIndexItem.Rotation.Horizontal, t.RelativeOrientation()); } [TestMethod] public void FileIndexItemTest_SetOrientationrelativeRotation0() { - var fileObject = new FileIndexItem {Orientation = FileIndexItem.Rotation.Horizontal}; + var fileObject = new FileIndexItem { Orientation = FileIndexItem.Rotation.Horizontal }; fileObject.SetRelativeOrientation(); - Assert.AreEqual(FileIndexItem.Rotation.Horizontal,fileObject.Orientation); + Assert.AreEqual(FileIndexItem.Rotation.Horizontal, fileObject.Orientation); } - + [TestMethod] public void FileIndexItemTest_OrientationrelativeRotation_270CwTest() { - var fileObject = new FileIndexItem {Orientation = FileIndexItem.Rotation.Rotate270Cw}; - Assert.AreEqual(FileIndexItem.Rotation.Horizontal,fileObject.RelativeOrientation(1)); + var fileObject = new FileIndexItem { Orientation = FileIndexItem.Rotation.Rotate270Cw }; + Assert.AreEqual(FileIndexItem.Rotation.Horizontal, fileObject.RelativeOrientation(1)); } - + [TestMethod] public void FileIndexItemTest_SetOrientationrelativeRotationMinus1() { - var t = new FileIndexItem {Orientation = FileIndexItem.Rotation.Horizontal}; - Assert.AreEqual(FileIndexItem.Rotation.Rotate270Cw,t.RelativeOrientation(-1)); + var t = new FileIndexItem { Orientation = FileIndexItem.Rotation.Horizontal }; + Assert.AreEqual(FileIndexItem.Rotation.Rotate270Cw, t.RelativeOrientation(-1)); } [TestMethod] public void FileIndexItemTest_SetOrientationrelativeRotationPlus1() { - var t = new FileIndexItem {Orientation = FileIndexItem.Rotation.Horizontal}; - Assert.AreEqual(FileIndexItem.Rotation.Rotate90Cw,t.RelativeOrientation(1)); + var t = new FileIndexItem { Orientation = FileIndexItem.Rotation.Horizontal }; + Assert.AreEqual(FileIndexItem.Rotation.Rotate90Cw, t.RelativeOrientation(1)); } - + [TestMethod] public void FileIndexItemTest_SetOrientationrelativeRotation_Rotate270Cw_Plus1() { - var t = new FileIndexItem {Orientation = FileIndexItem.Rotation.Rotate270Cw}; - Assert.AreEqual(FileIndexItem.Rotation.Horizontal,t.RelativeOrientation(1)); + var t = new FileIndexItem { Orientation = FileIndexItem.Rotation.Rotate270Cw }; + Assert.AreEqual(FileIndexItem.Rotation.Horizontal, t.RelativeOrientation(1)); } @@ -231,93 +238,97 @@ public void FileIndexItemTest_SetOrientationrelativeRotation_Rotate270Cw_Plus1() public void FileIndexItemTest_SetOrientationrelativeRelativeOrientation_Plus5() { // test not very good - var t = new FileIndexItem {Orientation = FileIndexItem.Rotation.Rotate270Cw}; - Assert.AreEqual(FileIndexItem.Rotation.Horizontal,t.RelativeOrientation(5)); + var t = new FileIndexItem { Orientation = FileIndexItem.Rotation.Rotate270Cw }; + Assert.AreEqual(FileIndexItem.Rotation.Horizontal, t.RelativeOrientation(5)); } [TestMethod] public void FileIndexItemTest_SetAbsoluteOrientation_DoNotChange() { var rotationItem = new FileIndexItem().SetAbsoluteOrientation(); - Assert.AreEqual(FileIndexItem.Rotation.DoNotChange,rotationItem); + Assert.AreEqual(FileIndexItem.Rotation.DoNotChange, rotationItem); } [TestMethod] public void FileIndexItemTest_SetAbsoluteOrientation_Rotate90Cw() { var rotationItem = new FileIndexItem().SetAbsoluteOrientation("6"); - Assert.AreEqual(FileIndexItem.Rotation.Rotate90Cw,rotationItem); + Assert.AreEqual(FileIndexItem.Rotation.Rotate90Cw, rotationItem); } [TestMethod] public void FileIndexItemTest_SetAbsoluteOrientation_Rotate180() { var rotationItem = new FileIndexItem().SetAbsoluteOrientation("3"); - Assert.AreEqual(FileIndexItem.Rotation.Rotate180,rotationItem); + Assert.AreEqual(FileIndexItem.Rotation.Rotate180, rotationItem); } - + [TestMethod] public void FileIndexItemTest_SetAbsoluteOrientation_Rotate270Cw() { var rotationItem = new FileIndexItem().SetAbsoluteOrientation("8"); - Assert.AreEqual(FileIndexItem.Rotation.Rotate270Cw,rotationItem); + Assert.AreEqual(FileIndexItem.Rotation.Rotate270Cw, rotationItem); } [TestMethod] public void FileIndexItemTest_colorDisplayName_WinnerAlt() { var colorDisplayName = EnumHelper.GetDisplayName(ColorClassParser.Color.WinnerAlt); - Assert.AreEqual("Winner Alt",colorDisplayName); + Assert.AreEqual("Winner Alt", colorDisplayName); } - + [TestMethod] public void FileIndexItemTest_MakeModel_UsingField() { - var item = new FileIndexItem{MakeModel = "Apple|iPhone SE|iPhone SE back camera 4.15mm f/2.2"}; + var item = new FileIndexItem + { + MakeModel = "Apple|iPhone SE|iPhone SE back camera 4.15mm f/2.2" + }; Assert.AreEqual("Apple", item.Make); - Assert.AreEqual("iPhone SE",item.Model); - Assert.AreEqual("back camera 4.15mm f/2.2",item.LensModel); + Assert.AreEqual("iPhone SE", item.Model); + Assert.AreEqual("back camera 4.15mm f/2.2", item.LensModel); } [TestMethod] public void LensModel_Defaults() { - var item = new FileIndexItem{MakeModel = string.Empty}; - Assert.AreEqual(string.Empty,item.LensModel); + var item = new FileIndexItem { MakeModel = string.Empty }; + Assert.AreEqual(string.Empty, item.LensModel); } + [TestMethod] public void LensModel_ShouldReplace() { - var item = new FileIndexItem{MakeModel = "test|Canon|Canon Lens"}; - Assert.AreEqual("Lens",item.LensModel); + var item = new FileIndexItem { MakeModel = "test|Canon|Canon Lens" }; + Assert.AreEqual("Lens", item.LensModel); } - + [TestMethod] public void LensModel_ShouldNotReplace() { - var item = new FileIndexItem{MakeModel = "test||Canon Lens"}; - Assert.AreEqual("Canon Lens",item.LensModel); + var item = new FileIndexItem { MakeModel = "test||Canon Lens" }; + Assert.AreEqual("Canon Lens", item.LensModel); } - + [TestMethod] public void FileIndexItemTest_MakeModel_UsingFieldNull() { - var item = new FileIndexItem{MakeModel = null}; + var item = new FileIndexItem { MakeModel = null }; Assert.AreEqual(string.Empty, item.Make); } - + [TestMethod] public void FileIndexItemTest_MakeModel_UsingFieldNullLensModel() { - var item = new FileIndexItem{MakeModel = null}; + var item = new FileIndexItem { MakeModel = null }; Assert.AreEqual(string.Empty, item.LensModel); } - + [TestMethod] public void FileIndexItemTest_MakeModel_IgnoreDashDash() { - var item = new FileIndexItem{MakeModel = null}; - item.SetMakeModel("----",0); + var item = new FileIndexItem { MakeModel = null }; + item.SetMakeModel("----", 0); Assert.AreEqual(string.Empty, item.LensModel); } @@ -344,7 +355,7 @@ public void FileIndexItemTest_SetMakeModel_Make() [TestMethod] public void FileIndexItemTest_SetMakeModel_MakeWrongPipeLength() { - var item = new FileIndexItem{MakeModel = "Apple|||||||"}; + var item = new FileIndexItem { MakeModel = "Apple|||||||" }; Assert.AreEqual(string.Empty, item.Make); Assert.AreEqual(string.Empty, item.Model); } @@ -394,13 +405,13 @@ public void FileIndexItemTest_SetMakeModel_RightOrder_MakeANDModel() public void FileIndexItemTest_IsRelativeOrientation() { var item = FileIndexItem.IsRelativeOrientation(-1); - Assert.AreEqual(true,item); - + Assert.AreEqual(true, item); + var item2 = FileIndexItem.IsRelativeOrientation(1); - Assert.AreEqual(true,item2); - + Assert.AreEqual(true, item2); + var item999 = FileIndexItem.IsRelativeOrientation(999); - Assert.AreEqual(false,item999); + Assert.AreEqual(false, item999); } @@ -408,42 +419,42 @@ public void FileIndexItemTest_IsRelativeOrientation() public void FileIndexItemTest_Ctor_SpaceName() { var item = new FileIndexItem("/test/image with space.jpg"); - Assert.AreEqual("image with space.jpg",item.FileName); - Assert.AreEqual("image with space",item.FileCollectionName); - Assert.AreEqual("/test",item.ParentDirectory); + Assert.AreEqual("image with space.jpg", item.FileName); + Assert.AreEqual("image with space", item.FileCollectionName); + Assert.AreEqual("/test", item.ParentDirectory); } [TestMethod] public void SidecarExtensions_read() { - var item = new FileIndexItem{SidecarExtensions = "xmp|test"}; + var item = new FileIndexItem { SidecarExtensions = "xmp|test" }; Assert.AreEqual("xmp", item.SidecarExtensionsList.FirstOrDefault()); } - + [TestMethod] public void SidecarExtensions_read_null() { - var item = new FileIndexItem{SidecarExtensions = null}; + var item = new FileIndexItem { SidecarExtensions = null }; Assert.AreEqual(0, item.SidecarExtensionsList.Count); } - + [TestMethod] public void SidecarExtensions_Add() { - var item = new FileIndexItem{SidecarExtensions = "xmp"}; + var item = new FileIndexItem { SidecarExtensions = "xmp" }; item.AddSidecarExtension("xmp"); - + Assert.AreEqual("xmp", item.SidecarExtensionsList.FirstOrDefault()); // no duplicates please Assert.AreEqual(1, item.SidecarExtensionsList.Count); } - + [TestMethod] public void SidecarExtensions_Remove() { - var item = new FileIndexItem{SidecarExtensions = "xmp"}; + var item = new FileIndexItem { SidecarExtensions = "xmp" }; item.RemoveSidecarExtension("xmp"); - + Assert.AreEqual(0, item.SidecarExtensionsList.Count); } @@ -452,69 +463,69 @@ public void SetFilePath_Home() { var item = new FileIndexItem(); item.SetFilePath("/"); - + Assert.AreEqual("/", item.FileName); Assert.AreEqual(string.Empty, item.ParentDirectory); } - + [TestMethod] public void SetFilePath_testFile() { var item = new FileIndexItem(); item.SetFilePath("/test.jpg"); - + Assert.AreEqual("test.jpg", item.FileName); Assert.AreEqual("/", item.ParentDirectory); } - + [TestMethod] public void SetFilePath_slashSlashTestFile() { var item = new FileIndexItem(); item.SetFilePath("//test.jpg"); - + Assert.AreEqual("test.jpg", item.FileName); Assert.AreEqual("/", item.ParentDirectory); Assert.AreEqual("/test.jpg", item.FilePath); } - + [TestMethod] public void SetFilePath_subFolderTestFile() { var item = new FileIndexItem(); item.SetFilePath("/test/test.jpg"); - + Assert.AreEqual("test.jpg", item.FileName); Assert.AreEqual("/test", item.ParentDirectory); } - - + + [TestMethod] public void Size_Lt_0() { var value = -1; - var item = new FileIndexItem(){Size = value}; - + var item = new FileIndexItem() { Size = value }; + Assert.AreEqual(0, item.Size); } - + [TestMethod] public void Size_MinValue() { - var item = new FileIndexItem(){Size = 99999999999999999}; + var item = new FileIndexItem() { Size = 99999999999999999 }; // overwrite here, should not be 0 item.Size = int.MinValue; - + // should write to large values to min value Assert.AreEqual(0, item.Size); } - + [TestMethod] public void Size_ShouldAdd() { var value = 2; - var item = new FileIndexItem(){Size = value}; - + var item = new FileIndexItem() { Size = value }; + Assert.AreEqual(2, item.Size); } @@ -523,17 +534,18 @@ public void FixedListToString_Null() { Assert.AreEqual(string.Empty, FileIndexItem.FixedListToString(null)); } - + [TestMethod] public void FixedListToString_One() { - Assert.AreEqual("test", FileIndexItem.FixedListToString(new List{"test"})); + Assert.AreEqual("test", FileIndexItem.FixedListToString(new List { "test" })); } - + [TestMethod] public void FixedListToString_Two() { - Assert.AreEqual("test|test2", FileIndexItem.FixedListToString(new List{"test","test2"})); + Assert.AreEqual("test|test2", + FileIndexItem.FixedListToString(new List { "test", "test2" })); } } } diff --git a/starsky/starskytest/starsky.foundation.database/QueryTest/QueryTest.cs b/starsky/starskytest/starsky.foundation.database/QueryTest/QueryTest.cs index 3a905f58e1..bdb4e28c7d 100644 --- a/starsky/starskytest/starsky.foundation.database/QueryTest/QueryTest.cs +++ b/starsky/starskytest/starsky.foundation.database/QueryTest/QueryTest.cs @@ -12,7 +12,7 @@ using starsky.foundation.database.Query; using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Models; -using starskycore.Attributes; +using starsky.project.web.Attributes; using starskytest.FakeMocks; namespace starskytest.starsky.foundation.database.QueryTest diff --git a/starsky/starskytest/Helpers/HttpClientHelperTest.cs b/starsky/starskytest/starsky.foundation.http/Helpers/HttpClientHelperTest.cs similarity index 80% rename from starsky/starskytest/Helpers/HttpClientHelperTest.cs rename to starsky/starskytest/starsky.foundation.http/Helpers/HttpClientHelperTest.cs index 2a3b71cdd0..a36e4179fd 100644 --- a/starsky/starskytest/Helpers/HttpClientHelperTest.cs +++ b/starsky/starskytest/starsky.foundation.http/Helpers/HttpClientHelperTest.cs @@ -10,7 +10,7 @@ using starskytest.FakeCreateAn; using starskytest.FakeMocks; -namespace starskytest.Helpers +namespace starskytest.starsky.foundation.http.Helpers { [TestClass] public sealed class HttpClientHelperTest @@ -27,15 +27,16 @@ public async Task Download_HttpClientHelperBadDomainDownload() services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); var scopeFactory = serviceProvider.GetRequiredService(); - - var httpClientHelper = new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); + + var httpClientHelper = + new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); // use only whitelisted domains var path = Path.Combine(new AppSettings().TempFolder, "pathToNOTdownload.txt"); - var output = await httpClientHelper.Download("http://mybadurl.cn",path); - Assert.AreEqual(false,output); + var output = await httpClientHelper.Download("http://mybadurl.cn", path); + Assert.AreEqual(false, output); } - + [TestMethod] public async Task Download_HttpClientHelper_404NotFoundTest() { @@ -48,15 +49,16 @@ public async Task Download_HttpClientHelper_404NotFoundTest() services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); var scopeFactory = serviceProvider.GetRequiredService(); - - var httpClientHelper = new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); + + var httpClientHelper = + new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); // there is an file written var path = Path.Combine(new CreateAnImage().BasePath, "file.txt"); - var output = await httpClientHelper.Download("https://download.geonames.org/404",path); - Assert.AreEqual(false,output); + var output = await httpClientHelper.Download("https://download.geonames.org/404", path); + Assert.AreEqual(false, output); } - + [TestMethod] public async Task Download_HttpClientHelper_HTTP_Not_Download() { @@ -69,15 +71,16 @@ public async Task Download_HttpClientHelper_HTTP_Not_Download() services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); var scopeFactory = serviceProvider.GetRequiredService(); - - var httpClientHelper = new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); + + var httpClientHelper = + new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); // http is not used anymore var path = Path.Combine(new AppSettings().TempFolder, "pathToNOTdownload.txt"); - var output = await httpClientHelper.Download("http://qdraw.nl",path); - Assert.AreEqual(false,output); + var output = await httpClientHelper.Download("http://qdraw.nl", path); + Assert.AreEqual(false, output); } - + [TestMethod] public async Task Download_HttpClientHelper_Download() { @@ -92,15 +95,17 @@ public async Task Download_HttpClientHelper_Download() var scopeFactory = serviceProvider.GetRequiredService(); var storageProvider = serviceProvider.GetRequiredService(); - var httpClientHelper = new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); + var httpClientHelper = + new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); // there is an file written var path = Path.Combine(new CreateAnImage().BasePath, "file.txt"); - var output = await httpClientHelper.Download("https://qdraw.nl/test",path); - - Assert.AreEqual(true,output); - - Assert.AreEqual(FolderOrFileModel.FolderOrFileTypeList.File,storageProvider.IsFolderOrFile(path)); + var output = await httpClientHelper.Download("https://qdraw.nl/test", path); + + Assert.AreEqual(true, output); + + Assert.AreEqual(FolderOrFileModel.FolderOrFileTypeList.File, + storageProvider.IsFolderOrFile(path)); storageProvider.FileDelete(path); } @@ -109,18 +114,20 @@ public async Task Download_HttpClientHelper_Download() public async Task Download_HttpClientHelper_Download_HttpRequestException() { // > next HttpRequestException - var fakeHttpMessageHandler = new FakeHttpMessageHandler(new HttpRequestException("should fail")); + var fakeHttpMessageHandler = + new FakeHttpMessageHandler(new HttpRequestException("should fail")); var httpClient = new HttpClient(fakeHttpMessageHandler); var httpProvider = new HttpProvider(httpClient); - + var services = new ServiceCollection(); services.AddSingleton(); services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); var scopeFactory = serviceProvider.GetRequiredService(); - - var httpClientHelper = new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); - var output = await httpClientHelper.Download("https://qdraw.nl/test","/sdkflndf",1); + + var httpClientHelper = + new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); + var output = await httpClientHelper.Download("https://qdraw.nl/test", "/sdkflndf", 1); Assert.IsFalse(output); } @@ -128,9 +135,10 @@ public async Task Download_HttpClientHelper_Download_HttpRequestException() [ExpectedException(typeof(EndOfStreamException))] public async Task Download_HttpClientHelper_Download_NoStorage() { - await new HttpClientHelper(new FakeIHttpProvider(), null, new FakeIWebLogger()).Download("t","T"); + await new HttpClientHelper(new FakeIHttpProvider(), null, new FakeIWebLogger()) + .Download("t", "T"); } - + [TestMethod] public async Task ReadString_HttpClientHelper_ReadString() { @@ -144,29 +152,32 @@ public async Task ReadString_HttpClientHelper_ReadString() var serviceProvider = services.BuildServiceProvider(); var scopeFactory = serviceProvider.GetRequiredService(); - var httpClientHelper = new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); + var httpClientHelper = + new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); var output = await httpClientHelper.ReadString("https://qdraw.nl/test"); - - Assert.AreEqual(true,output.Key); + + Assert.AreEqual(true, output.Key); } - - + + [TestMethod] public async Task ReadString_HttpClientHelper_ReadString_HttpRequestException() { // > next HttpRequestException - var fakeHttpMessageHandler = new FakeHttpMessageHandler(new HttpRequestException("should fail")); + var fakeHttpMessageHandler = + new FakeHttpMessageHandler(new HttpRequestException("should fail")); var httpClient = new HttpClient(fakeHttpMessageHandler); var httpProvider = new HttpProvider(httpClient); - + var services = new ServiceCollection(); services.AddSingleton(); services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); var scopeFactory = serviceProvider.GetRequiredService(); - - var httpClientHelper = new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); + + var httpClientHelper = + new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); var output = await httpClientHelper.ReadString("https://qdraw.nl/test"); Assert.IsFalse(output.Key); } @@ -183,14 +194,15 @@ public async Task ReadString_HttpClientHelper_HTTP_Not_ReadString() services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); var scopeFactory = serviceProvider.GetRequiredService(); - - var httpClientHelper = new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); + + var httpClientHelper = + new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); // http is not used anymore var output = await httpClientHelper.ReadString("http://qdraw.nl"); - Assert.AreEqual(false,output.Key); + Assert.AreEqual(false, output.Key); } - + [TestMethod] public async Task ReadString_HttpClientHelper_404NotFound_ReadString_Test() { @@ -203,13 +215,14 @@ public async Task ReadString_HttpClientHelper_404NotFound_ReadString_Test() services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); var scopeFactory = serviceProvider.GetRequiredService(); - - var httpClientHelper = new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); + + var httpClientHelper = + new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); var output = await httpClientHelper.ReadString("https://download.geonames.org/404"); - Assert.AreEqual(false,output.Key); + Assert.AreEqual(false, output.Key); } - + [TestMethod] public async Task PostString_HttpClientHelper() { @@ -223,14 +236,15 @@ public async Task PostString_HttpClientHelper() var serviceProvider = services.BuildServiceProvider(); var scopeFactory = serviceProvider.GetRequiredService(); - var httpClientHelper = new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); + var httpClientHelper = + new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); var output = await httpClientHelper .PostString("https://qdraw.nl/test", new StringContent(string.Empty)); - - Assert.AreEqual(true,output.Key); + + Assert.AreEqual(true, output.Key); } - + [TestMethod] public async Task PostString_HttpClientHelper_VerboseFalse() { @@ -245,30 +259,34 @@ public async Task PostString_HttpClientHelper_VerboseFalse() var scopeFactory = serviceProvider.GetRequiredService(); var fakeLogger = new FakeIWebLogger(); - var httpClientHelper = new HttpClientHelper(httpProvider, scopeFactory,fakeLogger); + var httpClientHelper = new HttpClientHelper(httpProvider, scopeFactory, fakeLogger); await httpClientHelper - .PostString("https://qdraw.nl/test", new StringContent(string.Empty),false); - - Assert.IsFalse(fakeLogger.TrackedInformation.Exists(p => p.Item2?.Contains("PostString") == true)); - Assert.IsFalse(fakeLogger.TrackedInformation.Exists(p => p.Item2?.Contains("HttpClientHelper") == true)); + .PostString("https://qdraw.nl/test", new StringContent(string.Empty), false); + + Assert.IsFalse( + fakeLogger.TrackedInformation.Exists(p => p.Item2?.Contains("PostString") == true)); + Assert.IsFalse(fakeLogger.TrackedInformation.Exists(p => + p.Item2?.Contains("HttpClientHelper") == true)); } - + [TestMethod] public async Task PostString_HttpRequestException() { // > next HttpRequestException - var fakeHttpMessageHandler = new FakeHttpMessageHandler(new HttpRequestException("should fail")); + var fakeHttpMessageHandler = + new FakeHttpMessageHandler(new HttpRequestException("should fail")); var httpClient = new HttpClient(fakeHttpMessageHandler); var httpProvider = new HttpProvider(httpClient); - + var services = new ServiceCollection(); services.AddSingleton(); services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); var scopeFactory = serviceProvider.GetRequiredService(); - - var httpClientHelper = new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); + + var httpClientHelper = + new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); var output = await httpClientHelper .PostString("https://qdraw.nl/test", new StringContent(string.Empty)); Assert.IsFalse(output.Key); @@ -286,15 +304,16 @@ public async Task PostString_HTTP_Not_ReadString() services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); var scopeFactory = serviceProvider.GetRequiredService(); - - var httpClientHelper = new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); + + var httpClientHelper = + new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); // http is not used anymore var output = await httpClientHelper .PostString("http://qdraw.nl", new StringContent(string.Empty)); - Assert.AreEqual(false,output.Key); + Assert.AreEqual(false, output.Key); } - + [TestMethod] public async Task PostString_404NotFound_Test() { @@ -307,14 +326,13 @@ public async Task PostString_404NotFound_Test() services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); var scopeFactory = serviceProvider.GetRequiredService(); - - var httpClientHelper = new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); + + var httpClientHelper = + new HttpClientHelper(httpProvider, scopeFactory, new FakeIWebLogger()); var output = await httpClientHelper .PostString("https://download.geonames.org/404", new StringContent(string.Empty)); - Assert.AreEqual(false,output.Key); + Assert.AreEqual(false, output.Key); } - - } } diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsUnixTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsUnixTests.cs index c0069a536e..85b5b5697a 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsUnixTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsSetFileAssociationsUnixTests.cs @@ -40,7 +40,7 @@ public void SetKeyDefaultValue__UnixOnly() } // Is false due its unix - var result = WindowsSetFileAssociations.SetKeyDefaultValue("test", "Test"); + var result = WindowsSetFileAssociations.SetKeyValue("test", "Test"); Assert.IsFalse(result); } } diff --git a/starsky/starskytest/starsky.foundation.platform/Helpers/ArgsHelperTest.cs b/starsky/starskytest/starsky.foundation.platform/Helpers/ArgsHelperTest.cs index c6237dca0a..68d51a7795 100644 --- a/starsky/starskytest/starsky.foundation.platform/Helpers/ArgsHelperTest.cs +++ b/starsky/starskytest/starsky.foundation.platform/Helpers/ArgsHelperTest.cs @@ -8,7 +8,7 @@ using starsky.foundation.platform.Extensions; using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Models; -using starskycore.Attributes; +using starsky.project.web.Attributes; using starskytest.FakeCreateAn; using starskytest.FakeMocks; @@ -29,11 +29,10 @@ public ArgsHelperTest() var newImage = new CreateAnImage(); var dict = new Dictionary { - { "App:StorageFolder", newImage.BasePath }, - { "App:Verbose", "true" } + { "App:StorageFolder", newImage.BasePath }, { "App:Verbose", "true" } }; // Start using dependency injection - var builder = new ConfigurationBuilder(); + var builder = new ConfigurationBuilder(); // Add random config to dependency injection builder.AddInMemoryCollection(dict); // build config @@ -45,86 +44,85 @@ public ArgsHelperTest() // get the service _appSettings = serviceProvider.GetRequiredService(); } - + [TestMethod] [ExcludeFromCoverage] public void ArgsHelper_NeedVerboseTest() { - var args = new List {"-v"}.ToArray(); + var args = new List { "-v" }.ToArray(); Assert.IsTrue(ArgsHelper.NeedVerbose(args)); - + // Bool parse check - args = new List {"-v","true"}.ToArray(); + args = new List { "-v", "true" }.ToArray(); Assert.IsTrue(ArgsHelper.NeedVerbose(args)); } - + [TestMethod] [ExcludeFromCoverage] public void ArgsHelper_NeedRecruisiveTest() { - var args = new List {"-r"}.ToArray(); + var args = new List { "-r" }.ToArray(); Assert.IsTrue(ArgsHelper.NeedRecursive(args)); - + // Bool parse check - args = new List {"-r","true"}.ToArray(); + args = new List { "-r", "true" }.ToArray(); Assert.IsTrue(ArgsHelper.NeedRecursive(args)); } - + [TestMethod] [ExcludeFromCoverage] public void ArgsHelper_NeedCacheCleanupTest() { - var args = new List {"-x"}.ToArray(); + var args = new List { "-x" }.ToArray(); Assert.IsTrue(ArgsHelper.NeedCleanup(args)); - + // Bool parse check - args = new List {"-x","true"}.ToArray(); - Assert.IsTrue( ArgsHelper.NeedCleanup(args)); + args = new List { "-x", "true" }.ToArray(); + Assert.IsTrue(ArgsHelper.NeedCleanup(args)); } - - + [TestMethod] [ExcludeFromCoverage] public void ArgsHelper_GetIndexModeTest() { // Default on so testing off - var args = new List {"-i","false"}.ToArray(); + var args = new List { "-i", "false" }.ToArray(); Assert.IsFalse(ArgsHelper.GetIndexMode(args)); } - - + + [TestMethod] [ExcludeFromCoverage] public void ArgsHelper_NeedHelpTest() { - var args = new List {"-h"}.ToArray(); + var args = new List { "-h" }.ToArray(); Assert.IsTrue(ArgsHelper.NeedHelp(args)); // Bool parse cheArgsHelper_GetPath_CurrentDirectory_Testck - args = new List {"-h","true"}.ToArray(); + args = new List { "-h", "true" }.ToArray(); Assert.IsTrue(ArgsHelper.NeedHelp(args)); } - + [TestMethod] public void ArgsHelper_GetPathFormArgsTest() { - var args = new List {"-p", "/"}.ToArray(); + var args = new List { "-p", "/" }.ToArray(); Assert.AreEqual("/", new ArgsHelper(_appSettings).GetPathFormArgs(args)); } - + [TestMethod] public void GetUserInputPassword() { - var args = new List {"-p", "test"}.ToArray(); - Assert.AreEqual("test",ArgsHelper.GetUserInputPassword(args)); + var args = new List { "-p", "test" }.ToArray(); + Assert.AreEqual("test", ArgsHelper.GetUserInputPassword(args)); } - + [TestMethod] public void GetUserInputPasswordLong() { - var args = new List {"--password", "test"}.ToArray(); - Assert.AreEqual("test",ArgsHelper.GetUserInputPassword(args)); + var args = new List { "--password", "test" }.ToArray(); + Assert.AreEqual("test", ArgsHelper.GetUserInputPassword(args)); } [TestMethod] @@ -132,45 +130,46 @@ public void GetUserInputPasswordLong() public void ArgsHelper_GetPathFormArgsTest_FieldAccessException() { // inject appSettings! - var args = new List {"-p", "/"}.ToArray(); + var args = new List { "-p", "/" }.ToArray(); new ArgsHelper(null!).GetPathFormArgs(args); } - + [TestMethod] public void GetPathListFormArgsTest_SingleItem() { - var args = new List {"-p", "/"}.ToArray(); - Assert.AreEqual("/",new ArgsHelper(_appSettings).GetPathListFormArgs(args).FirstOrDefault()); + var args = new List { "-p", "/" }.ToArray(); + Assert.AreEqual("/", + new ArgsHelper(_appSettings).GetPathListFormArgs(args).FirstOrDefault()); } - + [TestMethod] public void GetPathListFormArgsTest_MultipleItems() { - var args = new List {"-p", "\"/;/test\""}.ToArray(); + var args = new List { "-p", "\"/;/test\"" }.ToArray(); var result = new ArgsHelper(_appSettings).GetPathListFormArgs(args); - - Assert.AreEqual("/",result.FirstOrDefault()); - Assert.AreEqual("/test",result[1]); + + Assert.AreEqual("/", result.FirstOrDefault()); + Assert.AreEqual("/test", result[1]); } - + [TestMethod] public void GetPathListFormArgsTest_IgnoreNullOrWhiteSpace() { - var args = new List {"-p", "\"/;\""}.ToArray(); + var args = new List { "-p", "\"/;\"" }.ToArray(); var result = new ArgsHelper(_appSettings).GetPathListFormArgs(args); - + Assert.AreEqual(1, result.Count); - Assert.AreEqual("/",result.FirstOrDefault()); + Assert.AreEqual("/", result.FirstOrDefault()); } - + [TestMethod] public void GetPathListFormArgsTest_CurrentDirectory() { - var args = new List {"-p"}.ToArray(); + var args = new List { "-p" }.ToArray(); var result = new ArgsHelper(_appSettings).GetPathListFormArgs(args); - + Assert.AreEqual(1, result.Count); - Assert.AreEqual(Directory.GetCurrentDirectory(),result.FirstOrDefault()); + Assert.AreEqual(Directory.GetCurrentDirectory(), result.FirstOrDefault()); } [TestMethod] @@ -178,15 +177,15 @@ public void GetPathListFormArgsTest_CurrentDirectory() public void GetPathListFormArgsTest__FieldAccessException() { // inject appSettings! - var args = new List {"-p", "/"}.ToArray(); + var args = new List { "-p", "/" }.ToArray(); new ArgsHelper(null!).GetPathListFormArgs(args); } - + [TestMethod] public void ArgsHelper_GetPath_WithHelp_CurrentDirectory_Test() { var args = new List { "-p", "-h" }.ToArray(); - var value = new ArgsHelper(_appSettings).GetPathFormArgs(args,false); + var value = new ArgsHelper(_appSettings).GetPathFormArgs(args, false); var currentDir = Directory.GetCurrentDirectory(); Assert.AreEqual(currentDir, value); @@ -196,34 +195,34 @@ public void ArgsHelper_GetPath_WithHelp_CurrentDirectory_Test() public void ArgsHelper_GetPath_CurrentDirectory_Test() { var args = new List { "-p" }.ToArray(); - var value = new ArgsHelper(_appSettings).GetPathFormArgs(args,false); + var value = new ArgsHelper(_appSettings).GetPathFormArgs(args, false); Assert.AreEqual(Directory.GetCurrentDirectory(), value); } - + [TestMethod] [ExcludeFromCoverage] public void ArgsHelper_GetSubpathFormArgsTest() { _appSettings.StorageFolder = new CreateAnImage().BasePath; - var args = new List {"-s", "/"}.ToArray(); - Assert.AreEqual("/",ArgsHelper.GetSubPathFormArgs(args)); - } - + var args = new List { "-s", "/" }.ToArray(); + Assert.AreEqual("/", ArgsHelper.GetSubPathFormArgs(args)); + } + [TestMethod] [ExcludeFromCoverage] public void ArgsHelper_IfSubPathTest() { _appSettings.StorageFolder = new CreateAnImage().BasePath; - var args = new List {"-s", "/"}.ToArray(); + var args = new List { "-s", "/" }.ToArray(); Assert.IsTrue(ArgsHelper.IsSubPathOrPath(args)); - + // Default - args = new List{string.Empty}.ToArray(); + args = new List { string.Empty }.ToArray(); Assert.IsTrue(ArgsHelper.IsSubPathOrPath(args)); - - args = new List {"-p", "/"}.ToArray(); + + args = new List { "-p", "/" }.ToArray(); Assert.IsFalse(ArgsHelper.IsSubPathOrPath(args)); } @@ -231,7 +230,7 @@ public void ArgsHelper_IfSubPathTest() public void ArgsHelper_CurrentDirectory_IfSubpathTest() { // for selecting the current directory - var args = new List {"-p"}.ToArray(); + var args = new List { "-p" }.ToArray(); Assert.IsFalse(ArgsHelper.IsSubPathOrPath(args)); } @@ -240,55 +239,54 @@ public void ArgsHelper_CurrentDirectory_IfSubpathTest() public void ArgsHelper_GetThumbnailTest() { _appSettings.StorageFolder = new CreateAnImage().BasePath; - var args = new List {"-t", "true"}.ToArray(); + var args = new List { "-t", "true" }.ToArray(); Assert.IsTrue(ArgsHelper.GetThumbnail(args)); - } - + } + [TestMethod] [ExcludeFromCoverage] public void ArgsHelper_GetOrphanFolderCheckTest() { _appSettings.StorageFolder = new CreateAnImage().BasePath; - var args = new List {"-o", "true"}.ToArray(); + var args = new List { "-o", "true" }.ToArray(); Assert.IsTrue(new ArgsHelper(_appSettings).GetOrphanFolderCheck(args)); - } - + } + [TestMethod] [ExcludeFromCoverage] public void ArgsHelper_GetMoveTest() { - var args = new List {"-m"}.ToArray(); + var args = new List { "-m" }.ToArray(); Assert.IsTrue(ArgsHelper.GetMove(args)); - + // Bool parse check - args = new List {"-m","true"}.ToArray(); + args = new List { "-m", "true" }.ToArray(); Assert.IsTrue(ArgsHelper.GetMove(args)); } - + [TestMethod] public void ArgsHelper_GetMoveTest2() { // Bool parse check - var args = new List {"-m","false"}.ToArray(); + var args = new List { "-m", "false" }.ToArray(); Assert.IsFalse(ArgsHelper.GetMove(args)); } - + [TestMethod] [ExcludeFromCoverage] public void ArgsHelper_GetAllTest() { - var args = new List {"-a"}.ToArray(); + var args = new List { "-a" }.ToArray(); Assert.AreEqual(true, ArgsHelper.GetAll(args)); - + // Bool parse check - args = new List {"-a","false"}.ToArray(); + args = new List { "-a", "false" }.ToArray(); Assert.AreEqual(false, ArgsHelper.GetAll(args)); - + args = new List().ToArray(); Assert.AreEqual(false, ArgsHelper.GetAll(args)); - } - + [TestMethod] [ExcludeFromCoverage] public void ArgsHelper_SetEnvironmentByArgsShortTestListTest() @@ -297,72 +295,70 @@ public void ArgsHelper_SetEnvironmentByArgsShortTestListTest() var envNameList = new ArgsHelper(_appSettings).EnvNameList.ToArray(); var shortTestList = new List(); - for (int i = 0; i < shortNameList.Length; i++) + for ( int i = 0; i < shortNameList.Length; i++ ) { shortTestList.Add(shortNameList[i]); shortTestList.Add(i.ToString()); } - + new ArgsHelper(_appSettings).SetEnvironmentByArgs(shortTestList); - - for (int i = 0; i < envNameList.Length; i++) + + for ( int i = 0; i < envNameList.Length; i++ ) { - Assert.AreEqual(Environment.GetEnvironmentVariable(envNameList[i]),i.ToString()); + Assert.AreEqual(Environment.GetEnvironmentVariable(envNameList[i]), i.ToString()); } - + // Reset Environment after use - foreach (var t in envNameList) + foreach ( var t in envNameList ) { - Environment.SetEnvironmentVariable(t,string.Empty); + Environment.SetEnvironmentVariable(t, string.Empty); } - } - + [TestMethod] [ExcludeFromCoverage] public void ArgsHelper_SetEnvironmentByArgsLongTestListTest() { var longNameList = new ArgsHelper(_appSettings).LongNameList.ToArray(); var envNameList = new ArgsHelper(_appSettings).EnvNameList.ToArray(); - + var longTestList = new List(); - for (int i = 0; i < longNameList.Length; i++) + for ( int i = 0; i < longNameList.Length; i++ ) { longTestList.Add(longNameList[i]); longTestList.Add(i.ToString()); } - + new ArgsHelper(_appSettings).SetEnvironmentByArgs(longTestList); - for (int i = 0; i < envNameList.Length; i++) + for ( int i = 0; i < envNameList.Length; i++ ) { - Assert.AreEqual(Environment.GetEnvironmentVariable(envNameList[i]),i.ToString()); + Assert.AreEqual(Environment.GetEnvironmentVariable(envNameList[i]), i.ToString()); } - + // Reset Environment after use - foreach (var t in envNameList) + foreach ( var t in envNameList ) { - Environment.SetEnvironmentVariable(t,string.Empty); + Environment.SetEnvironmentVariable(t, string.Empty); } - } [TestMethod] public void ArgsHelper_GetSubPathRelativeTest() { - var args = new List {"--subpathrelative", "1"}.ToArray(); + var args = new List { "--subpathrelative", "1" }.ToArray(); var relative = new ArgsHelper(_appSettings).GetRelativeValue(args); Assert.AreEqual(-1, relative); } - + [TestMethod] public void ArgsHelper_GetSubPathRelativeTestMinusValue() { - var args = new List {"--subpathrelative", "-1"}.ToArray(); + var args = new List { "--subpathrelative", "-1" }.ToArray(); var relative = new ArgsHelper(_appSettings).GetRelativeValue(args); Assert.AreEqual(-1, relative); } - + [TestMethod] [ExpectedException(typeof(FieldAccessException))] public void ArgsHelper_GetSubPathRelative_Null_Test() @@ -374,49 +370,53 @@ public void ArgsHelper_GetSubPathRelative_Null_Test() [TestMethod] public void ArgsHelper_GetSubPathRelativeTestLargeInt() { - var args = new List {"--subpathrelative", "201801020"}.ToArray(); + var args = new List { "--subpathrelative", "201801020" }.ToArray(); var relative = new ArgsHelper(_appSettings).GetRelativeValue(args); Assert.IsNull(relative); } - + [TestMethod] public void ArgsHelper_NeedHelpShowDialog_Thumbnail() { var console = new FakeConsoleWrapper(); // Just simple show a console dialog - new ArgsHelper(new AppSettings { - ApplicationType = AppSettings.StarskyAppType.Thumbnail, - Verbose = true - },console) + new ArgsHelper( + new AppSettings + { + ApplicationType = AppSettings.StarskyAppType.Thumbnail, Verbose = true + }, console) .NeedHelpShowDialog(); Assert.IsTrue(console.WrittenLines[0].Contains("Thumbnail")); } - + [TestMethod] public void ArgsHelper_NeedHelpShowDialog_MetaThumbnail() { var console = new FakeConsoleWrapper(); // Just simple show a console dialog - new ArgsHelper(new AppSettings { - ApplicationType = AppSettings.StarskyAppType.MetaThumbnail, - Verbose = true - },console) + new ArgsHelper( + new AppSettings + { + ApplicationType = AppSettings.StarskyAppType.MetaThumbnail, + Verbose = true + }, console) .NeedHelpShowDialog(); Assert.IsTrue(console.WrittenLines[0].Contains("MetaThumbnail")); } - + [TestMethod] public void ArgsHelper_NeedHelpShowDialog_Admin() { var console = new FakeConsoleWrapper(); - new ArgsHelper(new AppSettings { - ApplicationType = AppSettings.StarskyAppType.Admin, - Verbose = true - },console) + new ArgsHelper( + new AppSettings + { + ApplicationType = AppSettings.StarskyAppType.Admin, Verbose = true + }, console) .NeedHelpShowDialog(); Assert.IsTrue(console.WrittenLines[0].Contains("Admin")); } - + [TestMethod] public void ArgsHelper_NeedHelpShowDialog_Geo() { @@ -425,21 +425,22 @@ public void ArgsHelper_NeedHelpShowDialog_Geo() { ApplicationType = AppSettings.StarskyAppType.Geo }; - new ArgsHelper(geoAppSettings,console) + new ArgsHelper(geoAppSettings, console) .NeedHelpShowDialog(); - + Assert.IsNotNull(geoAppSettings); Assert.IsTrue(console.WrittenLines[0].Contains("Geo")); } - + [TestMethod] public void ArgsHelper_NeedHelpShowDialog_WebHtml() { var console = new FakeConsoleWrapper(); - new ArgsHelper(new AppSettings { - ApplicationType = AppSettings.StarskyAppType.WebHtml, - Verbose = true - },console) + new ArgsHelper( + new AppSettings + { + ApplicationType = AppSettings.StarskyAppType.WebHtml, Verbose = true + }, console) .NeedHelpShowDialog(); Assert.IsTrue(console.WrittenLines[0].Contains("WebHtml")); } @@ -448,7 +449,9 @@ public void ArgsHelper_NeedHelpShowDialog_WebHtml() public void ArgsHelper_NeedHelpShowDialog_Importer() { var console = new FakeConsoleWrapper(); - new ArgsHelper(new AppSettings {ApplicationType = AppSettings.StarskyAppType.Importer},console) + new ArgsHelper( + new AppSettings { ApplicationType = AppSettings.StarskyAppType.Importer }, + console) .NeedHelpShowDialog(); Assert.IsTrue(console.WrittenLines[0].Contains("Importer")); } @@ -457,7 +460,8 @@ public void ArgsHelper_NeedHelpShowDialog_Importer() public void ArgsHelper_NeedHelpShowDialog_Sync() { var console = new FakeConsoleWrapper(); - new ArgsHelper(new AppSettings {ApplicationType = AppSettings.StarskyAppType.Sync},console) + new ArgsHelper(new AppSettings { ApplicationType = AppSettings.StarskyAppType.Sync }, + console) .NeedHelpShowDialog(); Assert.IsTrue(console.WrittenLines[0].Contains("Sync")); } @@ -467,33 +471,35 @@ public void NeedHelpShowDialog_WebHtml_Verbose() { var consoleWrapper = new FakeConsoleWrapper(); var appSettings = - new AppSettings { - Verbose = true, - ApplicationType = AppSettings.StarskyAppType.WebHtml, - PublishProfiles = new Dictionary>{ + new AppSettings + { + Verbose = true, + ApplicationType = AppSettings.StarskyAppType.WebHtml, + PublishProfiles = new Dictionary> { - "_d", new List { - new AppSettingsPublishProfiles + "_d", + new List { - Append = "_append", - Copy = true, - Folder = "folder" + new AppSettingsPublishProfiles + { + Append = "_append", Copy = true, Folder = "folder" + } } } - }} + } }; - - new ArgsHelper(appSettings, consoleWrapper ) + + new ArgsHelper(appSettings, consoleWrapper) .NeedHelpShowDialog(); var contains = consoleWrapper.WrittenLines.Contains( "--- Path: Append: _append Copy: True Folder: folder/ Prepend: Template: " + "ContentType: None MetaData: True OverlayMaxWidth: 100 SourceMaxWidth: 100 "); - + Assert.IsTrue(contains); } - + [TestMethod] [ExpectedException(typeof(FieldAccessException))] public void ArgsHelper_NeedHelpShowDialog_Null_Test() @@ -501,7 +507,7 @@ public void ArgsHelper_NeedHelpShowDialog_Null_Test() new ArgsHelper(null!).NeedHelpShowDialog(); // FieldAccessException } - + [TestMethod] [ExpectedException(typeof(FieldAccessException))] @@ -515,43 +521,44 @@ public void ArgsHelper_SetEnvironmentToAppSettings_Null_Test() public void ArgsHelper_SetEnvironmentToAppSettingsTest() { var appSettings = new AppSettings(); - - + + var shortNameList = new ArgsHelper(appSettings).ShortNameList.ToArray(); var envNameList = new ArgsHelper(appSettings).EnvNameList.ToArray(); var shortTestList = new List(); - for (int i = 0; i < envNameList.Length; i++) + for ( int i = 0; i < envNameList.Length; i++ ) { shortTestList.Add(shortNameList[i]); if ( envNameList[i] == "app__DatabaseType" ) { shortTestList.Add("InMemoryDatabase"); // need to exact good - continue; + continue; } if ( envNameList[i] == "app__Structure" ) { shortTestList.Add("/{filename}.ext"); - continue; + continue; } - + if ( envNameList[i] == "app__DatabaseConnection" ) { shortTestList.Add("test"); - continue; + continue; } - + if ( envNameList[i] == "app__ExifToolPath" ) { shortTestList.Add("app__ExifToolPath"); - continue; + continue; } + if ( envNameList[i] == "app__StorageFolder" ) { shortTestList.Add("app__StorageFolder"); - continue; + continue; } Console.WriteLine(envNameList[i]); @@ -567,46 +574,46 @@ public void ArgsHelper_SetEnvironmentToAppSettingsTest() shortTestList.Add(i.ToString()); } - + // First inject values to evn new ArgsHelper(appSettings).SetEnvironmentByArgs(shortTestList); - - + + // and now read it back new ArgsHelper(appSettings).SetEnvironmentToAppSettings(); - Assert.AreEqual("/{filename}.ext",appSettings.Structure); - Assert.AreEqual(AppSettings.DatabaseTypeList.InMemoryDatabase,appSettings.DatabaseType); - Assert.AreEqual("test",appSettings.DatabaseConnection); - Assert.AreEqual("app__ExifToolPath",appSettings.ExifToolPath); - Assert.AreEqual(true,appSettings.StorageFolder.Contains("app__StorageFolder")); + Assert.AreEqual("/{filename}.ext", appSettings.Structure); + Assert.AreEqual(AppSettings.DatabaseTypeList.InMemoryDatabase, + appSettings.DatabaseType); + Assert.AreEqual("test", appSettings.DatabaseConnection); + Assert.AreEqual("app__ExifToolPath", appSettings.ExifToolPath); + Assert.AreEqual(true, appSettings.StorageFolder.Contains("app__StorageFolder")); + - - // Reset Environment after use - foreach (var t in envNameList) + foreach ( var t in envNameList ) { - Environment.SetEnvironmentVariable(t,string.Empty); + Environment.SetEnvironmentVariable(t, string.Empty); } } - + [TestMethod] public void ArgsHelper_GetColorClass() { - var args = new List {"--colorclass", "1"}.ToArray(); + var args = new List { "--colorclass", "1" }.ToArray(); var value = ArgsHelper.GetColorClass(args); Assert.AreEqual(1, value); } - + [TestMethod] public void ArgsHelper_GetColorClass_99_Fallback() { - var args = new List {"--colorclass", "99"}.ToArray(); + var args = new List { "--colorclass", "99" }.ToArray(); var value = ArgsHelper.GetColorClass(args); Assert.AreEqual(-1, value); } - + [TestMethod] public void ArgsHelper_GetColorClassFallback() { @@ -614,13 +621,13 @@ public void ArgsHelper_GetColorClassFallback() var value = ArgsHelper.GetColorClass(args); Assert.AreEqual(-1, value); } - + [TestMethod] public void Name() { _appSettings.StorageFolder = new CreateAnImage().BasePath; - var args = new List {"-n", "test"}.ToArray(); - Assert.AreEqual("test",ArgsHelper.GetName(args)); - } + var args = new List { "-n", "test" }.ToArray(); + Assert.AreEqual("test", ArgsHelper.GetName(args)); + } } } diff --git a/starsky/starskytest/Helpers/Base64HelperTest.cs b/starsky/starskytest/starsky.foundation.platform/Helpers/Base64HelperTest.cs similarity index 94% rename from starsky/starskytest/Helpers/Base64HelperTest.cs rename to starsky/starskytest/starsky.foundation.platform/Helpers/Base64HelperTest.cs index 904039511b..da848ed78e 100644 --- a/starsky/starskytest/Helpers/Base64HelperTest.cs +++ b/starsky/starskytest/starsky.foundation.platform/Helpers/Base64HelperTest.cs @@ -3,7 +3,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.foundation.platform.Helpers; -namespace starskytest.Helpers +namespace starskytest.starsky.foundation.platform.Helpers { [TestClass] public sealed class Base64HelperTest @@ -39,6 +39,5 @@ public void Base64HelperTest_TryParseCorruptString() var noByte = Array.Empty(); Assert.AreEqual(noByte.Length, currupt.Length); } - } } diff --git a/starsky/starskytest/starsky.foundation.platform/Helpers/EnumHelperTest.cs b/starsky/starskytest/starsky.foundation.platform/Helpers/EnumHelperTest.cs deleted file mode 100644 index 5cd6cd8547..0000000000 --- a/starsky/starskytest/starsky.foundation.platform/Helpers/EnumHelperTest.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using starsky.foundation.platform.Helpers; - -namespace starskytest.starsky.foundation.platform.Helpers; - -[TestClass] -public class EnumHelperTest -{ - public enum TestValue - { - [Display(Name = "Test One")] - Value1, - - [Display(Name = "Test Two")] - Value2, - - Value3, - - [Display(Name = null)] - Value4 - } - - [TestMethod] - public void Test_GetDisplayName_ReturnsDisplayName_ForEnumWithDisplayName() - { - // Arrange - var enumValue = TestValue.Value1; - - // Act - var result = EnumHelper.GetDisplayName(enumValue); - - // Assert - Assert.AreEqual("Test One", result); - } - - [TestMethod] - public void Test_GetDisplayName_ReturnsEnumValue_ForEnumWithoutDisplayName() - { - // Arrange - var enumValue = TestValue.Value3; - - // Act - var result = EnumHelper.GetDisplayName(enumValue); - - // Assert - Assert.AreEqual(null, result); - } - - [TestMethod] - public void Test_GetDisplayName_ReturnsEmptyString_ForEnumWithNullDisplayName() - { - // Arrange - var enumValue = TestValue.Value4; - - // Act - var result = EnumHelper.GetDisplayName(enumValue); - - // Assert - Assert.AreEqual(null, result); - } - - [TestMethod] - public void Test_GetDisplayName_ReturnsEmptyString_ForNullEnum() - { - // Arrange - TestValue? enumValue = null; - - // Act - var result = EnumHelper.GetDisplayName(enumValue!); - - // Assert - Assert.AreEqual(null, result); - } - - [TestMethod] - public void Test_GetDisplayName_ReturnsDisplayName_ForNullableEnumWithDisplayName() - { - // Arrange - TestValue? enumValue = TestValue.Value1; - - // Act - var result = EnumHelper.GetDisplayName(enumValue); - - // Assert - Assert.AreEqual("Test One", result); - } - - [TestMethod] - public void Test_GetDisplayName_ReturnsEnumValue_ForNullableEnumWithoutDisplayName() - { - // Arrange - TestValue? enumValue = TestValue.Value3; - - // Act - var result = EnumHelper.GetDisplayName(enumValue); - - // Assert - Assert.AreEqual(null, result); - } - - [TestMethod] - public void Test_GetDisplayName_ReturnsEmptyString_ForNullableEnumWithNullDisplayName() - { - // Arrange - TestValue? enumValue = TestValue.Value4; - - // Act - var result = EnumHelper.GetDisplayName(enumValue); - - // Assert - Assert.AreEqual(null, result); - } - -} diff --git a/starsky/starskytest/starsky.foundation.platform/Helpers/PathHelper2Test.cs b/starsky/starskytest/starsky.foundation.platform/Helpers/PathHelper2Test.cs index ffa2f4b750..fe00ba6da3 100644 --- a/starsky/starskytest/starsky.foundation.platform/Helpers/PathHelper2Test.cs +++ b/starsky/starskytest/starsky.foundation.platform/Helpers/PathHelper2Test.cs @@ -1,19 +1,19 @@ ο»Ώusing System.IO; using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.foundation.platform.Helpers; -using starskycore.Attributes; +using starsky.project.web.Attributes; namespace starskytest.starsky.foundation.platform.Helpers { [TestClass] public sealed class PathHelper2Test { - [ExcludeFromCoverage] [TestMethod] public void ConfigRead_RemoveLatestBackslashTest() { - var input = PathHelper.RemoveLatestBackslash("/2018"+ Path.DirectorySeparatorChar.ToString()); + var input = + PathHelper.RemoveLatestBackslash("/2018" + Path.DirectorySeparatorChar.ToString()); var output = "/2018"; Assert.AreEqual(input, output); } @@ -26,7 +26,7 @@ public void ConfigRead_PrefixDbslashTest() var output = "/2018/"; Assert.AreEqual(input, output); } - + [ExcludeFromCoverage] [TestMethod] public void ConfigRead_AddBackslashTest() @@ -34,9 +34,9 @@ public void ConfigRead_AddBackslashTest() var input = PathHelper.AddBackslash("2018"); var output = "2018" + Path.DirectorySeparatorChar.ToString(); Assert.AreEqual(input, output); - + input = PathHelper.AddBackslash("2018" + Path.DirectorySeparatorChar.ToString()); - output = "2018"+ Path.DirectorySeparatorChar.ToString(); + output = "2018" + Path.DirectorySeparatorChar.ToString(); Assert.AreEqual(input, output); } @@ -44,8 +44,7 @@ public void ConfigRead_AddBackslashTest() [TestMethod] public void ConfigRead_RemovePrefixDbSlashTest() { - Assert.AreEqual("2018",PathHelper.RemovePrefixDbSlash("/2018")); + Assert.AreEqual("2018", PathHelper.RemovePrefixDbSlash("/2018")); } - } } diff --git a/starsky/starskytest/starsky.foundation.platform/Helpers/PortProgramHelperTest.cs b/starsky/starskytest/starsky.foundation.platform/Helpers/PortProgramHelperTest.cs deleted file mode 100644 index 51ee3536ee..0000000000 --- a/starsky/starskytest/starsky.foundation.platform/Helpers/PortProgramHelperTest.cs +++ /dev/null @@ -1,175 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using starsky.foundation.platform.Helpers; -using starsky.foundation.platform.Models; -using starsky.foundation.storage.Helpers; -using starsky.foundation.storage.Storage; - -namespace starskytest.starsky.foundation.platform.Helpers; - -[TestClass] -public class PortProgramHelperTest -{ - private readonly string? _prePort; - private readonly string? _preAspNetUrls; - - public PortProgramHelperTest() - { - _prePort = Environment.GetEnvironmentVariable("PORT"); - _preAspNetUrls = Environment.GetEnvironmentVariable("ASPNETCORE_URLS"); - } - - [TestMethod] - public void SetEnvPortAspNetUrls_ShouldSet() - { - Environment.SetEnvironmentVariable("PORT","8000"); - Environment.SetEnvironmentVariable("ASPNETCORE_URLS",""); - - PortProgramHelper.SetEnvPortAspNetUrls(new List()); - - Assert.AreEqual("http://*:8000",Environment.GetEnvironmentVariable("ASPNETCORE_URLS")); - - Environment.SetEnvironmentVariable("PORT",_prePort); - Environment.SetEnvironmentVariable("ASPNETCORE_URLS",_preAspNetUrls); - } - - [TestMethod] - public async Task SetEnvPortAspNetUrlsAndSetDefault_ShouldSet() - { - Environment.SetEnvironmentVariable("PORT","8000"); - Environment.SetEnvironmentVariable("ASPNETCORE_URLS",""); - - await PortProgramHelper.SetEnvPortAspNetUrlsAndSetDefault(Array.Empty(),string.Empty); - Assert.AreEqual("http://*:8000",Environment.GetEnvironmentVariable("ASPNETCORE_URLS")); - - Environment.SetEnvironmentVariable("PORT",_prePort); - Environment.SetEnvironmentVariable("ASPNETCORE_URLS",_preAspNetUrls); - } - - [TestMethod] - public void SetEnvPortAspNetUrls_ShouldIgnore() - { - Environment.SetEnvironmentVariable("PORT",""); - Environment.SetEnvironmentVariable("ASPNETCORE_URLS",""); - - PortProgramHelper.SetEnvPortAspNetUrls(new List()); - Assert.AreEqual(null,Environment.GetEnvironmentVariable("ASPNETCORE_URLS")); - - Environment.SetEnvironmentVariable("PORT",_prePort); - Environment.SetEnvironmentVariable("ASPNETCORE_URLS",_preAspNetUrls); - } - - [TestMethod] - public async Task SetEnvPortAspNetUrlsAndSetDefault_ShouldIgnore_DueAppSettingsFile1() - { - Environment.SetEnvironmentVariable("PORT",""); - Environment.SetEnvironmentVariable("ASPNETCORE_URLS",""); - - var appSettingsPath = Path.Combine(new AppSettings().BaseDirectoryProject,"appsettings-222.json"); - var stream = StringToStreamHelper.StringToStream("{ \"Kestrel\": {\n \"Endpoints\": {\n " + - " \"Https\": {\n \"Url\": \"https://*:8001\"\n },\n \"Http\": {\n " + - " \"Url\": \"http://*:8000\"\n }\n }\n }\n }"); - await new StorageHostFullPathFilesystem().WriteStreamAsync(stream,appSettingsPath); - - await PortProgramHelper.SetEnvPortAspNetUrlsAndSetDefault(Array.Empty(),appSettingsPath); - - Assert.AreEqual(null,Environment.GetEnvironmentVariable("ASPNETCORE_URLS")); - - Environment.SetEnvironmentVariable("PORT",_prePort); - Environment.SetEnvironmentVariable("ASPNETCORE_URLS",_preAspNetUrls); - - // remove afterwards - new StorageHostFullPathFilesystem().FileDelete(appSettingsPath); - } - - - [TestMethod] - public async Task SkipForAppSettingsJsonFile_ShouldIgnore_DueAppSettingsFile() - { - Environment.SetEnvironmentVariable("PORT",""); - Environment.SetEnvironmentVariable("ASPNETCORE_URLS",""); - - var appSettingsPath = Path.Combine(new AppSettings().BaseDirectoryProject,"appsettings-111.json"); - var stream = StringToStreamHelper.StringToStream("{ \"Kestrel\": {\n \"Endpoints\": {\n " + - " \"Https\": {\n \"Url\": \"https://*:8001\"\n },\n \"Http\": {\n " + - " \"Url\": \"http://*:8000\"\n }\n }\n }\n }"); - await new StorageHostFullPathFilesystem().WriteStreamAsync(stream,appSettingsPath); - - var result = await PortProgramHelper.SkipForAppSettingsJsonFile(appSettingsPath); - - Assert.AreEqual(true,result); - Assert.AreEqual(null,Environment.GetEnvironmentVariable("ASPNETCORE_URLS")); - - Environment.SetEnvironmentVariable("PORT",_prePort); - Environment.SetEnvironmentVariable("ASPNETCORE_URLS",_preAspNetUrls); - - // remove afterwards - new StorageHostFullPathFilesystem().FileDelete(appSettingsPath); - } - - [TestMethod] - public async Task SkipForAppSettingsJsonFile_ShouldIgnore_DueAppSettingsFile2() - { - Environment.SetEnvironmentVariable("PORT",""); - Environment.SetEnvironmentVariable("ASPNETCORE_URLS",""); - - var appSettingsPath = Path.Combine(new AppSettings().BaseDirectoryProject,"appsettings-333.json"); - var stream = StringToStreamHelper.StringToStream("{ \"Kestrel\": {\n \"Endpoints\": {\n " + - " \"Https\": {\n \"Url\": \"https://*:8001\"\n }\n " + - "\n }\n }\n }"); - await new StorageHostFullPathFilesystem().WriteStreamAsync(stream,appSettingsPath); - - var result = await PortProgramHelper.SkipForAppSettingsJsonFile(appSettingsPath); - - Assert.AreEqual(true,result); - Assert.AreEqual(null,Environment.GetEnvironmentVariable("ASPNETCORE_URLS")); - - Environment.SetEnvironmentVariable("PORT",_prePort); - Environment.SetEnvironmentVariable("ASPNETCORE_URLS",_preAspNetUrls); - - // remove afterwards - new StorageHostFullPathFilesystem().FileDelete(appSettingsPath); - } - - - [TestMethod] - public async Task SkipForAppSettingsJsonFile_ShouldFalse() - { - var result = await PortProgramHelper.SkipForAppSettingsJsonFile(string.Empty); - Assert.AreEqual(false,result); - } - - [TestMethod] - public void SetDefaultAspNetCoreUrls_ShouldSet() - { - Environment.SetEnvironmentVariable("PORT",""); - Environment.SetEnvironmentVariable("ASPNETCORE_URLS",""); - - PortProgramHelper.SetDefaultAspNetCoreUrls(Array.Empty()); - - // should set to default - Assert.AreEqual("http://localhost:4000;https://localhost:4001", - Environment.GetEnvironmentVariable("ASPNETCORE_URLS")); - - Environment.SetEnvironmentVariable("PORT",_prePort); - Environment.SetEnvironmentVariable("ASPNETCORE_URLS",_preAspNetUrls); - } - - [TestMethod] - public void SetDefaultAspNetCoreUrls_ShouldIgnore() - { - Environment.SetEnvironmentVariable("PORT",""); - Environment.SetEnvironmentVariable("ASPNETCORE_URLS","http://localhost:4000"); - - PortProgramHelper.SetDefaultAspNetCoreUrls(Array.Empty()); - - // should set port to 4000 - Assert.AreEqual("http://localhost:4000",Environment.GetEnvironmentVariable("ASPNETCORE_URLS")); - - Environment.SetEnvironmentVariable("PORT",_prePort); - Environment.SetEnvironmentVariable("ASPNETCORE_URLS",_preAspNetUrls); - } -} diff --git a/starsky/starskytest/starsky.foundation.readmeta/Services/ReadMeta_ExifReadTest.cs b/starsky/starskytest/starsky.foundation.readmeta/Services/ReadMeta_ExifReadTest.cs index 116b724fd0..3e2c6ff125 100644 --- a/starsky/starskytest/starsky.foundation.readmeta/Services/ReadMeta_ExifReadTest.cs +++ b/starsky/starskytest/starsky.foundation.readmeta/Services/ReadMeta_ExifReadTest.cs @@ -12,7 +12,7 @@ using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Models; using starsky.foundation.readmeta.ReadMetaHelpers; -using starskycore.Attributes; +using starsky.project.web.Attributes; using starskytest.FakeCreateAn; using starskytest.FakeMocks; using XmpCore; @@ -39,21 +39,20 @@ public MockDirectory(Dictionary tagNameMap) : base(tagNameMap) [TestClass] public sealed class ExifReadTest { - [TestMethod] public void ExifRead_GetObjectNameNull() { - var t = ReadMetaExif.GetObjectName(new List{new MockDirectory(null!)}); - Assert.AreEqual( string.Empty,t); + var t = ReadMetaExif.GetObjectName(new List { new MockDirectory(null!) }); + Assert.AreEqual(string.Empty, t); } [TestMethod] public void ExifRead_GetObjectNameTest() { var dir = new IptcDirectory(); - dir.Set(IptcDirectory.TagObjectName, "test" ); - var t = ReadMetaExif.GetObjectName(new List{dir}); - Assert.AreEqual("test",t); + dir.Set(IptcDirectory.TagObjectName, "test"); + var t = ReadMetaExif.GetObjectName(new List { dir }); + Assert.AreEqual("test", t); } [TestMethod] @@ -62,29 +61,29 @@ public void ExifRead_GetCaptionAbstractTest() { var dir = new IptcDirectory(); dir.Set(IptcDirectory.TagCaption, "test123"); - var t = ReadMetaExif.GetCaptionAbstract(new List{dir}); + var t = ReadMetaExif.GetCaptionAbstract(new List { dir }); Assert.AreEqual("test123", t); } - + [TestMethod] public void ExifRead_GetExifKeywordsSingleTest() { var dir = new IptcDirectory(); dir.Set(IptcDirectory.TagKeywords, "test123"); - var t = ReadMetaExif.GetExifKeywords(new List{dir}); + var t = ReadMetaExif.GetExifKeywords(new List { dir }); Assert.AreEqual("test123", t); } - + [TestMethod] public void ExifRead_GetExifKeywordsMultipleTest() { var dir = new IptcDirectory(); dir.Set(IptcDirectory.TagKeywords, "test123;test12"); - var t = ReadMetaExif.GetExifKeywords(new List{dir}); - Assert.AreEqual("test123, test12",t); //with space + var t = ReadMetaExif.GetExifKeywords(new List { dir }); + Assert.AreEqual("test123, test12", t); //with space } - + [TestMethod] public void ExifRead_GetExifDateTimeTest() { @@ -96,10 +95,12 @@ public void ExifRead_GetExifDateTimeTest() dir2.Set(ExifDirectoryBase.TagDateTimeOriginal, "2010:12:12 12:41:35"); dir2.Set(ExifDirectoryBase.TagDateTime, "2010:12:12 12:41:35"); container.Add(dir2); - - var result = new ReadMetaExif(null!,null!, new FakeIWebLogger()).GetExifDateTime(container); - var expectedExifDateTime = new DateTime(2010, 12, 12, 12, 41, 35, kind: DateTimeKind.Local); - + + var result = + new ReadMetaExif(null!, null!, new FakeIWebLogger()).GetExifDateTime(container); + var expectedExifDateTime = + new DateTime(2010, 12, 12, 12, 41, 35, kind: DateTimeKind.Local); + Assert.AreEqual(expectedExifDateTime, result); } @@ -110,65 +111,73 @@ public void ExifRead_GetExifDateTimeTest_TagDateTimeOriginal() var dir2 = new ExifSubIfdDirectory(); dir2.Set(ExifDirectoryBase.TagDateTimeOriginal, "2010:12:12 12:41:35"); container.Add(dir2); - - var result = new ReadMetaExif(null!,null!, new FakeIWebLogger()).GetExifDateTime(container); - var expectedExifDateTime = new DateTime(2010, 12, 12, 12, 41, 35, kind: DateTimeKind.Local); - + + var result = + new ReadMetaExif(null!, null!, new FakeIWebLogger()).GetExifDateTime(container); + var expectedExifDateTime = + new DateTime(2010, 12, 12, 12, 41, 35, kind: DateTimeKind.Local); + Assert.AreEqual(expectedExifDateTime, result); } - + [TestMethod] public void ExifRead_GetExifDateTimeTest_QuickTimeMovieHeaderDirectory_SetUtc() { var orgCulture = CultureInfo.CurrentCulture.ThreeLetterISOLanguageName; CultureInfo.CurrentCulture = new CultureInfo("EN-us"); - + var container = new List(); var dir2 = new QuickTimeMovieHeaderDirectory(); dir2.Set(QuickTimeMovieHeaderDirectory.TagCreated, "Tue Oct 11 09:40:04 2011"); container.Add(dir2); - - var result = new ReadMetaExif(null!, new AppSettings{ VideoUseLocalTime = new List - { - new CameraMakeModel("test","test") - }, - CameraTimeZone = "Europe/London" - },null!).GetExifDateTime(container, new CameraMakeModel("test","test")); - - var expectedExifDateTime = new DateTime(2011, 10, 11, 9, 40, 4, kind: DateTimeKind.Local); - + + var result = + new ReadMetaExif(null!, + new AppSettings + { + VideoUseLocalTime = + new List { new CameraMakeModel("test", "test") }, + CameraTimeZone = "Europe/London" + }, null!).GetExifDateTime(container, new CameraMakeModel("test", "test")); + + var expectedExifDateTime = + new DateTime(2011, 10, 11, 9, 40, 4, kind: DateTimeKind.Local); + Assert.AreEqual(expectedExifDateTime, result); - + CultureInfo.CurrentCulture = new CultureInfo(orgCulture); } - + [TestMethod] public void ExifRead_GetExifDateTimeTest_QuickTimeMovieHeaderDirectory_BrandOnly() { var orgCulture = CultureInfo.CurrentCulture.ThreeLetterISOLanguageName; CultureInfo.CurrentCulture = new CultureInfo("EN-us"); - + var container = new List(); var dir2 = new QuickTimeMovieHeaderDirectory(); dir2.Set(QuickTimeMovieHeaderDirectory.TagCreated, "Tue Oct 11 09:40:04 2011"); container.Add(dir2); - - var result = new ReadMetaExif(null!, new AppSettings{ VideoUseLocalTime = new List - { - new CameraMakeModel("test", string.Empty) - }, - CameraTimeZone = "Europe/London" - }, new FakeIWebLogger()).GetExifDateTime(container, new CameraMakeModel("test","test")); - - var expectedExifDateTime = new DateTime(2011, 10, 11, 9, 40, 4, kind: DateTimeKind.Local); - + + var result = new ReadMetaExif(null!, + new AppSettings + { + VideoUseLocalTime = + new List { new CameraMakeModel("test", string.Empty) }, + CameraTimeZone = "Europe/London" + }, new FakeIWebLogger()) + .GetExifDateTime(container, new CameraMakeModel("test", "test")); + + var expectedExifDateTime = + new DateTime(2011, 10, 11, 9, 40, 4, kind: DateTimeKind.Local); + Assert.AreEqual(expectedExifDateTime, result); - + CultureInfo.CurrentCulture = new CultureInfo(orgCulture); } - + [TestMethod] [ExcludeFromCoverage] public void ExifRead_GetExifDateTimeTest_QuickTimeMovieHeaderDirectory_AssumeLocal() @@ -176,26 +185,31 @@ public void ExifRead_GetExifDateTimeTest_QuickTimeMovieHeaderDirectory_AssumeLoc var orgCulture = CultureInfo.CurrentCulture.ThreeLetterISOLanguageName; CultureInfo.CurrentCulture = new CultureInfo("EN-us"); - + var container = new List(); var dir2 = new QuickTimeMovieHeaderDirectory(); dir2.Set(QuickTimeMovieHeaderDirectory.TagCreated, "Tue Oct 11 09:40:04 2011"); container.Add(dir2); - - var result = new ReadMetaExif(null!, new AppSettings{ VideoUseLocalTime = new List + + var result = new ReadMetaExif(null!, + new AppSettings { - new CameraMakeModel("Apple", string.Empty) - }, - CameraTimeZone = "Europe/London" - }, new FakeIWebLogger()).GetExifDateTime(container); - + VideoUseLocalTime = + new List + { + new CameraMakeModel("Apple", string.Empty) + }, + CameraTimeZone = "Europe/London" + }, new FakeIWebLogger()).GetExifDateTime(container); + CultureInfo.CurrentCulture = new CultureInfo(orgCulture); - var expectedExifDateTime = new DateTime(2011, 10, 11, 10, 40, 4, kind: DateTimeKind.Local); - + var expectedExifDateTime = + new DateTime(2011, 10, 11, 10, 40, 4, kind: DateTimeKind.Local); + Assert.AreEqual(expectedExifDateTime, result); } - + [TestMethod] [ExcludeFromCoverage] public void ExifRead_GetExifDateTimeTest_GetXmpData() @@ -208,13 +222,16 @@ public void ExifRead_GetExifDateTimeTest_GetXmpData() throw new NullReferenceException( "ExifRead_GetExifDateTimeTest_GetXmpData xmpMeta Field"); } - - dir2.XmpMeta.SetProperty("http://ns.adobe.com/photoshop/1.0/", "photoshop:DateCreated","2020-03-14T14:00:51" ); + + dir2.XmpMeta.SetProperty("http://ns.adobe.com/photoshop/1.0/", "photoshop:DateCreated", + "2020-03-14T14:00:51"); container.Add(dir2); - - var result = new ReadMetaExif(null!,null!, new FakeIWebLogger()).GetExifDateTime(container); - var expectedExifDateTime = new DateTime(2020, 3, 14, 14, 0, 51, kind: DateTimeKind.Local); - + + var result = + new ReadMetaExif(null!, null!, new FakeIWebLogger()).GetExifDateTime(container); + var expectedExifDateTime = + new DateTime(2020, 3, 14, 14, 0, 51, kind: DateTimeKind.Local); + Assert.AreEqual(expectedExifDateTime, result); } @@ -222,7 +239,7 @@ public void ExifRead_GetExifDateTimeTest_GetXmpData() public void ParseSubIfdDateTime_NotInFirstContainer_TagDateTimeOriginal() { var container = new List(); - + // for raw the first container does not contain dates var dir1 = new ExifSubIfdDirectory(); container.Add(dir1); @@ -233,14 +250,15 @@ public void ParseSubIfdDateTime_NotInFirstContainer_TagDateTimeOriginal() var provider = CultureInfo.InvariantCulture; var result = ReadMetaExif.ParseSubIfdDateTime(container, provider); - Assert.AreEqual(new DateTime(2022,02,02,20,22,02, kind: DateTimeKind.Local),result); + Assert.AreEqual(new DateTime(2022, 02, 02, 20, 22, 02, kind: DateTimeKind.Local), + result); } - + [TestMethod] public void ParseSubIfdDateTime_NotInFirstContainer_TagDateTimeDigitized() { var container = new List(); - + // for raw the first container does not contain dates var dir1 = new ExifSubIfdDirectory(); container.Add(dir1); @@ -251,9 +269,10 @@ public void ParseSubIfdDateTime_NotInFirstContainer_TagDateTimeDigitized() var provider = CultureInfo.InvariantCulture; var result = ReadMetaExif.ParseSubIfdDateTime(container, provider); - Assert.AreEqual(new DateTime(2022,02,02,20,22,02, kind: DateTimeKind.Local),result); + Assert.AreEqual(new DateTime(2022, 02, 02, 20, 22, 02, kind: DateTimeKind.Local), + result); } - + [TestMethod] public void ParseSubIfdDateTime_NonValidDate() { @@ -263,91 +282,100 @@ public void ParseSubIfdDateTime_NonValidDate() dir1.Set(ExifDirectoryBase.TagDateTimeDigitized, "test_not_valid_date"); container.Add(dir1); - + var provider = CultureInfo.InvariantCulture; var result = ReadMetaExif.ParseSubIfdDateTime(container, provider); - Assert.AreEqual(new DateTime(),result); + Assert.AreEqual(new DateTime(), result); } - + [TestMethod] public void ParseSubIfdDateTime_Nothing() { var container = new List(); var dir1 = new ExifSubIfdDirectory(); container.Add(dir1); - + var provider = CultureInfo.InvariantCulture; var result = ReadMetaExif.ParseSubIfdDateTime(container, provider); - Assert.AreEqual(new DateTime(),result); + Assert.AreEqual(new DateTime(), result); } [TestMethod] public void ExifRead_ReadExifFromFileTest() { var newImage = CreateAnImage.Bytes.ToArray(); - var fakeStorage = new FakeIStorage(new List{"/"}, - new List{"/test.jpg"},new List{newImage}); - - var item = new ReadMetaExif(fakeStorage,null!, new FakeIWebLogger()).ReadExifFromFile("/test.jpg"); - + var fakeStorage = new FakeIStorage(new List { "/" }, + new List { "/test.jpg" }, new List { newImage }); + + var item = + new ReadMetaExif(fakeStorage, null!, new FakeIWebLogger()).ReadExifFromFile( + "/test.jpg"); + Assert.AreEqual(ColorClassParser.Color.None, item.ColorClass); - Assert.AreEqual("caption", item.Description ); - Assert.AreEqual(false,item.IsDirectory ); + Assert.AreEqual("caption", item.Description); + Assert.AreEqual(false, item.IsDirectory); Assert.AreEqual("test, sion", item.Tags); Assert.AreEqual("title", item.Title); Assert.AreEqual(52.308205555500003, item.Latitude, 0.000001); - Assert.AreEqual(6.1935555554999997, item.Longitude, 0.000001); + Assert.AreEqual(6.1935555554999997, item.Longitude, 0.000001); Assert.AreEqual(2, item.ImageHeight); - Assert.AreEqual(3,item.ImageWidth); + Assert.AreEqual(3, item.ImageWidth); Assert.AreEqual("Diepenveen", item.LocationCity); - Assert.AreEqual( "Overijssel", item.LocationState); - Assert.AreEqual( "Nederland",item.LocationCountry); - Assert.AreEqual( 6,item.LocationAltitude); + Assert.AreEqual("Overijssel", item.LocationState); + Assert.AreEqual("Nederland", item.LocationCountry); + Assert.AreEqual(6, item.LocationAltitude); Assert.AreEqual(100, item.FocalLength); - Assert.AreEqual(new DateTime(2018,04,22,16,14,54, kind: DateTimeKind.Local), item.DateTime); - - Assert.AreEqual( "Sony|SLT-A58|24-105mm F3.5-4.5", item.MakeModel); - Assert.AreEqual( "Sony", item.Make); - Assert.AreEqual( "SLT-A58", item.Model); - Assert.AreEqual( "24-105mm F3.5-4.5", item.LensModel); - Assert.AreEqual( ImageStabilisationType.Unknown, item.ImageStabilisation); + Assert.AreEqual(new DateTime(2018, 04, 22, 16, 14, 54, kind: DateTimeKind.Local), + item.DateTime); + + Assert.AreEqual("Sony|SLT-A58|24-105mm F3.5-4.5", item.MakeModel); + Assert.AreEqual("Sony", item.Make); + Assert.AreEqual("SLT-A58", item.Model); + Assert.AreEqual("24-105mm F3.5-4.5", item.LensModel); + Assert.AreEqual(ImageStabilisationType.Unknown, item.ImageStabilisation); } - + [TestMethod] public void ImageStabilisationOn() { var newImage = CreateAnImageA6600.Bytes.ToArray(); - var fakeStorage = new FakeIStorage(new List{"/"}, - new List{"/test.jpg"},new List{newImage}); - - var item = new ReadMetaExif(fakeStorage,null!, new FakeIWebLogger()).ReadExifFromFile("/test.jpg"); - Assert.AreEqual( ImageStabilisationType.On, item.ImageStabilisation); + var fakeStorage = new FakeIStorage(new List { "/" }, + new List { "/test.jpg" }, new List { newImage }); + + var item = + new ReadMetaExif(fakeStorage, null!, new FakeIWebLogger()).ReadExifFromFile( + "/test.jpg"); + Assert.AreEqual(ImageStabilisationType.On, item.ImageStabilisation); } - + [TestMethod] public void ImageStabilisationOff() { var newImage = CreateAnImageA58Tamron.Bytes.ToArray(); - var fakeStorage = new FakeIStorage(new List{"/"}, - new List{"/test.jpg"},new List{newImage}); - - var item = new ReadMetaExif(fakeStorage,null!, new FakeIWebLogger()).ReadExifFromFile("/test.jpg"); - Assert.AreEqual( ImageStabilisationType.Off, item.ImageStabilisation); + var fakeStorage = new FakeIStorage(new List { "/" }, + new List { "/test.jpg" }, new List { newImage }); + + var item = + new ReadMetaExif(fakeStorage, null!, new FakeIWebLogger()).ReadExifFromFile( + "/test.jpg"); + Assert.AreEqual(ImageStabilisationType.Off, item.ImageStabilisation); } - + [TestMethod] public void LocationCountryCode() { var newImage = CreateAnImageA6600.Bytes.ToArray(); - var fakeStorage = new FakeIStorage(new List{"/"}, - new List{"/test.jpg"},new List{newImage}); - - var item = new ReadMetaExif(fakeStorage,new AppSettings{Verbose = true}, new FakeIWebLogger()).ReadExifFromFile("/test.jpg"); - Assert.AreEqual( "NLD", item.LocationCountryCode); + var fakeStorage = new FakeIStorage(new List { "/" }, + new List { "/test.jpg" }, new List { newImage }); + + var item = + new ReadMetaExif(fakeStorage, new AppSettings { Verbose = true }, + new FakeIWebLogger()).ReadExifFromFile("/test.jpg"); + Assert.AreEqual("NLD", item.LocationCountryCode); } - + [TestMethod] public void LocationCountryCode_ListDir() { @@ -359,41 +387,45 @@ public void LocationCountryCode_ListDir() " NLD\n" + " \n\n" + "\n"; - + var xmpMeta = XmpMetaFactory.ParseFromString(xmpData); var properties = xmpMeta.Properties.Any(); Assert.IsTrue(properties); - + var container = new List(); var dir2 = new XmpDirectory(); dir2.SetXmpMeta(xmpMeta); container.Add(dir2); - var result = ReadMetaExif.GetLocationCountryCode(container); - - Assert.AreEqual( "NLD", result); + var result = ReadMetaExif.GetLocationCountryCode(container); + + Assert.AreEqual("NLD", result); } - + [TestMethod] public void LensModelTamRon() { var newImage = CreateAnImageA58Tamron.Bytes.ToArray(); - var fakeStorage = new FakeIStorage(new List{"/"}, - new List{"/test.jpg"},new List{newImage}); - - var item = new ReadMetaExif(fakeStorage,null!, new FakeIWebLogger()).ReadExifFromFile("/test.jpg"); - Assert.AreEqual( "Tamron or Sigma Lens", item.LensModel); + var fakeStorage = new FakeIStorage(new List { "/" }, + new List { "/test.jpg" }, new List { newImage }); + + var item = + new ReadMetaExif(fakeStorage, null!, new FakeIWebLogger()).ReadExifFromFile( + "/test.jpg"); + Assert.AreEqual("Tamron or Sigma Lens", item.LensModel); } [TestMethod] public void ExifRead_ReadExifFromFileTest_DeletedTag() { var newImage = CreateAnImageStatusDeleted.Bytes.ToArray(); - var fakeStorage = new FakeIStorage(new List{"/"}, - new List{"/test.jpg"},new List{newImage}); - - var item = new ReadMetaExif(fakeStorage,null!, new FakeIWebLogger()).ReadExifFromFile("/test.jpg"); + var fakeStorage = new FakeIStorage(new List { "/" }, + new List { "/test.jpg" }, new List { newImage }); + + var item = + new ReadMetaExif(fakeStorage, null!, new FakeIWebLogger()).ReadExifFromFile( + "/test.jpg"); Assert.AreEqual(TrashKeyword.TrashKeywordString, item.Tags); } @@ -401,202 +433,224 @@ public void ExifRead_ReadExifFromFileTest_DeletedTag() public void ExifRead_ReadExif_FromPngInFileXMP_FileTest() { var newImage = CreateAnPng.Bytes.ToArray(); - var fakeStorage = new FakeIStorage(new List{"/"}, - new List{"/test.png"},new List{newImage}); - - var item = new ReadMetaExif(fakeStorage,null!, new FakeIWebLogger()).ReadExifFromFile("/test.png"); + var fakeStorage = new FakeIStorage(new List { "/" }, + new List { "/test.png" }, new List { newImage }); + + var item = + new ReadMetaExif(fakeStorage, null!, new FakeIWebLogger()).ReadExifFromFile( + "/test.png"); Assert.AreEqual(ColorClassParser.Color.SuperiorAlt, item.ColorClass); - Assert.AreEqual("Description", item.Description ); - Assert.AreEqual(false,item.IsDirectory ); + Assert.AreEqual("Description", item.Description); + Assert.AreEqual(false, item.IsDirectory); Assert.AreEqual("tags", item.Tags); Assert.AreEqual("title", item.Title); Assert.AreEqual(35.0379999999, item.Latitude, 0.000001); - Assert.AreEqual(-81.0520000001, item.Longitude, 0.000001); + Assert.AreEqual(-81.0520000001, item.Longitude, 0.000001); Assert.AreEqual(1, item.ImageHeight); - Assert.AreEqual(1,item.ImageWidth); + Assert.AreEqual(1, item.ImageWidth); Assert.AreEqual("City", item.LocationCity); - Assert.AreEqual( "State", item.LocationState); - Assert.AreEqual( "Country",item.LocationCountry); - Assert.AreEqual( 10,item.LocationAltitude); + Assert.AreEqual("State", item.LocationState); + Assert.AreEqual("Country", item.LocationCountry); + Assert.AreEqual(10, item.LocationAltitude); Assert.AreEqual(80, item.FocalLength); - Assert.AreEqual(new DateTime(2022,06,12,10,45,31, kind: DateTimeKind.Local), item.DateTime); + Assert.AreEqual(new DateTime(2022, 06, 12, 10, 45, 31, kind: DateTimeKind.Local), + item.DateTime); } - + [TestMethod] public void ExifRead_GetImageWidthHeight_returnNothing() { - var directory = new List {BuildDirectory(new List())}; - var returnNothing = ReadMetaExif.GetImageWidthHeight(directory,true); - Assert.AreEqual(0,returnNothing); - - var returnNothingFalse = ReadMetaExif.GetImageWidthHeight(directory,false); - Assert.AreEqual(0,returnNothingFalse); + var directory = new List { BuildDirectory(new List()) }; + var returnNothing = ReadMetaExif.GetImageWidthHeight(directory, true); + Assert.AreEqual(0, returnNothing); + + var returnNothingFalse = ReadMetaExif.GetImageWidthHeight(directory, false); + Assert.AreEqual(0, returnNothingFalse); } [TestMethod] public void ExifRead_ReadExif_FromQuickTimeMp4InFileXMP_FileTest() { var newImage = CreateAnQuickTimeMp4.Bytes; - var fakeStorage = new FakeIStorage(new List {"/"}, - new List {"/test.mp4"}, new List {newImage.ToArray()}); + var fakeStorage = new FakeIStorage(new List { "/" }, + new List { "/test.mp4" }, new List { newImage.ToArray() }); - var item = new ReadMetaExif(fakeStorage, new AppSettings{VideoUseLocalTime = new List - { - new CameraMakeModel("Apple","MacbookPro15,1") - }}, new FakeIWebLogger()).ReadExifFromFile("/test.mp4"); + var item = new ReadMetaExif(fakeStorage, + new AppSettings + { + VideoUseLocalTime = new List + { + new CameraMakeModel("Apple", "MacbookPro15,1") + } + }, new FakeIWebLogger()).ReadExifFromFile("/test.mp4"); var date = new DateTime(2020, 03, 29, 13, 10, 07, kind: DateTimeKind.Local); Assert.AreEqual(date, item.DateTime); Assert.AreEqual(20, item.ImageWidth); Assert.AreEqual(20, item.ImageHeight); - Assert.AreEqual(false,item.IsDirectory ); + Assert.AreEqual(false, item.IsDirectory); } - + [TestMethod] public void ExifRead_ReadExif_FromQuickTimeMp4InFileXMP_FileTest_DutchCulture() { - var currentCultureThreeLetterIsoLanguageName = CultureInfo.CurrentCulture.ThreeLetterISOLanguageName; + var currentCultureThreeLetterIsoLanguageName = + CultureInfo.CurrentCulture.ThreeLetterISOLanguageName; CultureInfo.CurrentCulture = new CultureInfo("NL-nl"); - + var newImage = CreateAnQuickTimeMp4.Bytes; - var fakeStorage = new FakeIStorage(new List {"/"}, - new List {"/test.mp4"}, new List {newImage.ToArray()}); + var fakeStorage = new FakeIStorage(new List { "/" }, + new List { "/test.mp4" }, new List { newImage.ToArray() }); - var item = new ReadMetaExif(fakeStorage, new AppSettings{VideoUseLocalTime = new List - { - new CameraMakeModel("Apple","MacbookPro15,1") - }}, new FakeIWebLogger()).ReadExifFromFile("/test.mp4"); + var item = new ReadMetaExif(fakeStorage, + new AppSettings + { + VideoUseLocalTime = new List + { + new CameraMakeModel("Apple", "MacbookPro15,1") + } + }, new FakeIWebLogger()).ReadExifFromFile("/test.mp4"); var date = new DateTime(2020, 03, 29, 13, 10, 07, kind: DateTimeKind.Local); Assert.AreEqual(date, item.DateTime); Assert.AreEqual(20, item.ImageWidth); Assert.AreEqual(20, item.ImageHeight); - Assert.AreEqual(false,item.IsDirectory ); - + Assert.AreEqual(false, item.IsDirectory); + CultureInfo.CurrentCulture = new CultureInfo(currentCultureThreeLetterIsoLanguageName); } [TestMethod] public void ExifRead_ParseQuickTimeDateTime_AssumeUtc_CameraTimeZoneMissing() { - var currentCultureThreeLetterIsoLanguageName = CultureInfo.CurrentCulture.ThreeLetterISOLanguageName; + var currentCultureThreeLetterIsoLanguageName = + CultureInfo.CurrentCulture.ThreeLetterISOLanguageName; CultureInfo.CurrentCulture = new CultureInfo("EN-us"); - + // CameraTimeZone = "Europe/London" is missing var fakeStorage = new FakeIStorage(); - - var item = new ReadMetaExif(fakeStorage,null!, new FakeIWebLogger()); + + var item = new ReadMetaExif(fakeStorage, null!, new FakeIWebLogger()); var dir = new QuickTimeMovieHeaderDirectory(); - dir.Set(QuickTimeMovieHeaderDirectory.TagCreated, "Tue Oct 11 09:40:04 2011" ); - + dir.Set(QuickTimeMovieHeaderDirectory.TagCreated, "Tue Oct 11 09:40:04 2011"); + var result = item.ParseQuickTimeDateTime(new CameraMakeModel(), - new List{dir}); - - var expectedExifDateTime = new DateTime(2011, 10, 11, 9, 40, 4, kind: DateTimeKind.Local); + new List { dir }); + + var expectedExifDateTime = + new DateTime(2011, 10, 11, 9, 40, 4, kind: DateTimeKind.Local); CultureInfo.CurrentCulture = new CultureInfo(currentCultureThreeLetterIsoLanguageName); Assert.AreEqual(expectedExifDateTime, result); } - + [TestMethod] public void ExifRead_ParseQuickTimeDateTime_UseLocalTime() { - var currentCultureThreeLetterIsoLanguageName = CultureInfo.CurrentCulture.ThreeLetterISOLanguageName; + var currentCultureThreeLetterIsoLanguageName = + CultureInfo.CurrentCulture.ThreeLetterISOLanguageName; CultureInfo.CurrentCulture = new CultureInfo("EN-us"); - + var fakeStorage = new FakeIStorage(); - var item = new ReadMetaExif(fakeStorage, new AppSettings - { - VideoUseLocalTime = new List{new CameraMakeModel("test","test")}, - CameraTimeZone = "Europe/London" - }, new FakeIWebLogger()); + var item = new ReadMetaExif(fakeStorage, + new AppSettings + { + VideoUseLocalTime = + new List { new CameraMakeModel("test", "test") }, + CameraTimeZone = "Europe/London" + }, new FakeIWebLogger()); var dir = new QuickTimeMovieHeaderDirectory(); - dir.Set(QuickTimeMovieHeaderDirectory.TagCreated, "Tue Oct 11 09:40:04 2011" ); - - var result = item.ParseQuickTimeDateTime(new CameraMakeModel("test","test"), - new List{dir}); - - var expectedExifDateTime = new DateTime(2011, 10, 11, 9, 40, 4, kind: DateTimeKind.Local); + dir.Set(QuickTimeMovieHeaderDirectory.TagCreated, "Tue Oct 11 09:40:04 2011"); + + var result = item.ParseQuickTimeDateTime(new CameraMakeModel("test", "test"), + new List { dir }); + + var expectedExifDateTime = + new DateTime(2011, 10, 11, 9, 40, 4, kind: DateTimeKind.Local); CultureInfo.CurrentCulture = new CultureInfo(currentCultureThreeLetterIsoLanguageName); Assert.AreEqual(expectedExifDateTime, result); } - + [TestMethod] public void ExifRead_ParseQuickTimeDateTime_UseLocalTime1_WithTimeZone() { - var currentCultureThreeLetterIsoLanguageName = CultureInfo.CurrentCulture.ThreeLetterISOLanguageName; + var currentCultureThreeLetterIsoLanguageName = + CultureInfo.CurrentCulture.ThreeLetterISOLanguageName; CultureInfo.CurrentCulture = new CultureInfo("EN-us"); - + var fakeStorage = new FakeIStorage(); - var item = new ReadMetaExif(fakeStorage, new AppSettings - { - VideoUseLocalTime = new List(), - CameraTimeZone = "Europe/London" - }, new FakeIWebLogger()); + var item = new ReadMetaExif(fakeStorage, + new AppSettings + { + VideoUseLocalTime = new List(), + CameraTimeZone = "Europe/London" + }, new FakeIWebLogger()); var dir = new QuickTimeMovieHeaderDirectory(); - dir.Set(QuickTimeMovieHeaderDirectory.TagCreated, "Tue Oct 11 09:40:04 2011" ); - - var result = item.ParseQuickTimeDateTime(new CameraMakeModel("test","test"), - new List{dir}); - - var expectedExifDateTime = new DateTime(2011, 10, 11, 10, 40, 4, kind: DateTimeKind.Local); + dir.Set(QuickTimeMovieHeaderDirectory.TagCreated, "Tue Oct 11 09:40:04 2011"); + + var result = item.ParseQuickTimeDateTime(new CameraMakeModel("test", "test"), + new List { dir }); + + var expectedExifDateTime = + new DateTime(2011, 10, 11, 10, 40, 4, kind: DateTimeKind.Local); CultureInfo.CurrentCulture = new CultureInfo(currentCultureThreeLetterIsoLanguageName); Assert.AreEqual(expectedExifDateTime, result); } - + [TestMethod] public void ExifRead_ParseQuickTimeDateTime_UseLocalTime_WithTimeZone_Wrong() { - var currentCultureThreeLetterIsoLanguageName = CultureInfo.CurrentCulture.ThreeLetterISOLanguageName; + var currentCultureThreeLetterIsoLanguageName = + CultureInfo.CurrentCulture.ThreeLetterISOLanguageName; CultureInfo.CurrentCulture = new CultureInfo("EN-us"); var fakeStorage = new FakeIStorage(); - var item = new ReadMetaExif(fakeStorage, new AppSettings - { - VideoUseLocalTime = new List(), - CameraTimeZone = "" - }, new FakeIWebLogger()); + var item = new ReadMetaExif(fakeStorage, + new AppSettings + { + VideoUseLocalTime = new List(), CameraTimeZone = "" + }, new FakeIWebLogger()); var dir = new QuickTimeMovieHeaderDirectory(); - dir.Set(QuickTimeMovieHeaderDirectory.TagCreated, "Tue Oct 11 09:40:04 2011" ); - - var result = item.ParseQuickTimeDateTime(new CameraMakeModel("test","test"), - new List{dir}); - - var expectedExifDateTime = new DateTime(2011, 10, 11, 9, 40, 4, kind: DateTimeKind.Utc).ToLocalTime(); + dir.Set(QuickTimeMovieHeaderDirectory.TagCreated, "Tue Oct 11 09:40:04 2011"); + + var result = item.ParseQuickTimeDateTime(new CameraMakeModel("test", "test"), + new List { dir }); + + var expectedExifDateTime = + new DateTime(2011, 10, 11, 9, 40, 4, kind: DateTimeKind.Utc).ToLocalTime(); CultureInfo.CurrentCulture = new CultureInfo(currentCultureThreeLetterIsoLanguageName); Assert.AreEqual(expectedExifDateTime, result); } - + [TestMethod] public void ExifRead_ParseQuickTimeDateTime_NoVideoUsedSet() { - var currentCultureThreeLetterIsoLanguageName = CultureInfo.CurrentCulture.ThreeLetterISOLanguageName; + var currentCultureThreeLetterIsoLanguageName = + CultureInfo.CurrentCulture.ThreeLetterISOLanguageName; CultureInfo.CurrentCulture = new CultureInfo("EN-us"); var fakeStorage = new FakeIStorage(); - var item = new ReadMetaExif(fakeStorage, new AppSettings - { - VideoUseLocalTime = null, - CameraTimeZone = "" - }, new FakeIWebLogger()); + var item = new ReadMetaExif(fakeStorage, + new AppSettings { VideoUseLocalTime = null, CameraTimeZone = "" }, + new FakeIWebLogger()); var dir = new QuickTimeMovieHeaderDirectory(); - dir.Set(QuickTimeMovieHeaderDirectory.TagCreated, "Tue Oct 11 09:40:04 2011" ); - - var result = item.ParseQuickTimeDateTime(new CameraMakeModel("test","test"), - new List{dir}); - - var expectedExifDateTime = new DateTime(2011, 10, 11, + dir.Set(QuickTimeMovieHeaderDirectory.TagCreated, "Tue Oct 11 09:40:04 2011"); + + var result = item.ParseQuickTimeDateTime(new CameraMakeModel("test", "test"), + new List { dir }); + + var expectedExifDateTime = new DateTime(2011, 10, 11, 9, 40, 4, kind: DateTimeKind.Utc).ToLocalTime(); CultureInfo.CurrentCulture = new CultureInfo(currentCultureThreeLetterIsoLanguageName); @@ -606,57 +660,64 @@ public void ExifRead_ParseQuickTimeDateTime_NoVideoUsedSet() [TestMethod] public void ExifRead_ReadExif_FromQuickTimeMp4InFileXMP_WithLocation_FileTest_DutchCulture() { - var currentCultureThreeLetterIsoLanguageName = CultureInfo.CurrentCulture.ThreeLetterISOLanguageName; + var currentCultureThreeLetterIsoLanguageName = + CultureInfo.CurrentCulture.ThreeLetterISOLanguageName; CultureInfo.CurrentCulture = new CultureInfo("NL-nl"); - + var newImage = CreateAnQuickTimeMp4.BytesWithLocation.ToArray(); - var fakeStorage = new FakeIStorage(new List {"/"}, - new List {"/test.mp4"}, new List {newImage}); + var fakeStorage = new FakeIStorage(new List { "/" }, + new List { "/test.mp4" }, new List { newImage }); - var item = new ReadMetaExif(fakeStorage,null!, new FakeIWebLogger()).ReadExifFromFile("/test.mp4"); + var item = + new ReadMetaExif(fakeStorage, null!, new FakeIWebLogger()).ReadExifFromFile( + "/test.mp4"); var date = new DateTime(2020, 04, 04, 12, 50, 19, DateTimeKind.Local).ToLocalTime(); Assert.AreEqual(date, item.DateTime); Assert.AreEqual(640, item.ImageWidth); Assert.AreEqual(360, item.ImageHeight); - - Assert.AreEqual(52.23829861111111, item.Latitude,0.001); - Assert.AreEqual(6.025800238715278, item.Longitude,0.001); - Assert.AreEqual(false,item.IsDirectory ); + Assert.AreEqual(52.23829861111111, item.Latitude, 0.001); + Assert.AreEqual(6.025800238715278, item.Longitude, 0.001); + + Assert.AreEqual(false, item.IsDirectory); CultureInfo.CurrentCulture = new CultureInfo(currentCultureThreeLetterIsoLanguageName); } - + [TestMethod] public void ExifRead_ReadExif_FromQuickTimeMp4InFileXMP_WithLocation_FileTest() { var newImage = CreateAnQuickTimeMp4.BytesWithLocation.ToArray(); - var fakeStorage = new FakeIStorage(new List {"/"}, - new List {"/test.mp4"}, new List {newImage}); + var fakeStorage = new FakeIStorage(new List { "/" }, + new List { "/test.mp4" }, new List { newImage }); - var item = new ReadMetaExif(fakeStorage,null!, new FakeIWebLogger()).ReadExifFromFile("/test.mp4"); + var item = + new ReadMetaExif(fakeStorage, null!, new FakeIWebLogger()).ReadExifFromFile( + "/test.mp4"); var date = new DateTime(2020, 04, 04, 12, 50, 19, DateTimeKind.Local).ToLocalTime(); Assert.AreEqual(date, item.DateTime); Assert.AreEqual(640, item.ImageWidth); Assert.AreEqual(360, item.ImageHeight); - - Assert.AreEqual(52.23829861111111, item.Latitude,0.001); - Assert.AreEqual(6.025800238715278, item.Longitude,0.001); - Assert.AreEqual(false,item.IsDirectory ); + Assert.AreEqual(52.23829861111111, item.Latitude, 0.001); + Assert.AreEqual(6.025800238715278, item.Longitude, 0.001); + + Assert.AreEqual(false, item.IsDirectory); } [TestMethod] public void ExifRead_DataParsingCorruptFailsData() { var newImage = CreateAnPng.Bytes.Take(200).ToArray(); // corrupt - var fakeStorage = new FakeIStorage(new List{"/"}, - new List{"/test.png"},new List{newImage}); - - var item = new ReadMetaExif(fakeStorage,null!, new FakeIWebLogger()).ReadExifFromFile("/test.png"); - + var fakeStorage = new FakeIStorage(new List { "/" }, + new List { "/test.png" }, new List { newImage }); + + var item = + new ReadMetaExif(fakeStorage, null!, new FakeIWebLogger()).ReadExifFromFile( + "/test.png"); + Assert.AreEqual(FileIndexItem.ExifStatus.OperationNotSupported, item.Status); Assert.AreEqual(ExtensionRolesHelper.ImageFormat.unknown, item.ImageFormat); } @@ -664,11 +725,13 @@ public void ExifRead_DataParsingCorruptFailsData() [TestMethod] public void ExifRead_DataParsingCorruptStreamNull() { - var fakeStorage = new FakeIStorage(new List{"/"}, - new List{"/test.png"},new List{null!}); - var item = new ReadMetaExif(fakeStorage,null!, new FakeIWebLogger()).ReadExifFromFile("/test.png"); + var fakeStorage = new FakeIStorage(new List { "/" }, + new List { "/test.png" }, new List { null! }); + var item = + new ReadMetaExif(fakeStorage, null!, new FakeIWebLogger()).ReadExifFromFile( + "/test.png"); // streamNull - + Assert.AreEqual(FileIndexItem.ExifStatus.OperationNotSupported, item.Status); Assert.AreEqual(FileIndexItem.ExifStatus.OperationNotSupported, item.Status); } @@ -682,12 +745,12 @@ private static MockDirectory BuildDirectory(IEnumerable values) { var directory = new MockDirectory(null!); - foreach (var pair in Enumerable.Range(1, int.MaxValue).Zip(values, Tuple.Create)) + foreach ( var pair in Enumerable.Range(1, int.MaxValue).Zip(values, Tuple.Create) ) directory.Set(pair.Item1, pair.Item2); return directory; } - + // ExifDirectoryBase.TagOrientation // "Top, left side (Horizontal / normal)", -- 1 // "Top, right side (Mirror horizontal)", -- 2 @@ -701,122 +764,121 @@ private static MockDirectory BuildDirectory(IEnumerable values) [TestMethod] public void GetOrientationFromExifItem_1() { - var dir3 = new ExifIfd0Directory(); dir3.Set(ExifDirectoryBase.TagOrientation, 1); // "Top, left side (Horizontal / normal)", -- 1 - var rotation = ReadMetaExif.GetOrientationFromExifItem(new List{dir3}); - Assert.AreEqual(FileIndexItem.Rotation.Horizontal,rotation); + var rotation = ReadMetaExif.GetOrientationFromExifItem(new List { dir3 }); + Assert.AreEqual(FileIndexItem.Rotation.Horizontal, rotation); } - + [TestMethod] public void GetOrientationFromExifItem_2() { - var dir3 = new ExifIfd0Directory(); dir3.Set(ExifDirectoryBase.TagOrientation, 2); - var rotation = ReadMetaExif.GetOrientationFromExifItem(new List{dir3}); + var rotation = ReadMetaExif.GetOrientationFromExifItem(new List { dir3 }); // 2 = unsuppored yet // "Top, right side (Mirror horizontal)", - Assert.AreEqual(FileIndexItem.Rotation.Horizontal,rotation); + Assert.AreEqual(FileIndexItem.Rotation.Horizontal, rotation); } - + [TestMethod] public void GetOrientationFromExifItem_3() { var dir3 = new ExifIfd0Directory(); dir3.Set(ExifDirectoryBase.TagOrientation, 3); // "Bottom, right side (Rotate 180)" - var rotation = ReadMetaExif.GetOrientationFromExifItem(new List{dir3}); - Assert.AreEqual(FileIndexItem.Rotation.Rotate180,rotation); + var rotation = ReadMetaExif.GetOrientationFromExifItem(new List { dir3 }); + Assert.AreEqual(FileIndexItem.Rotation.Rotate180, rotation); } - + [TestMethod] public void GetOrientationFromExifItem_4() { var dir3 = new ExifIfd0Directory(); dir3.Set(ExifDirectoryBase.TagOrientation, 4); - var rotation = ReadMetaExif.GetOrientationFromExifItem(new List{dir3}); + var rotation = ReadMetaExif.GetOrientationFromExifItem(new List { dir3 }); // Bottom, left side (Mirror vertical) - Assert.AreEqual(FileIndexItem.Rotation.Horizontal,rotation); + Assert.AreEqual(FileIndexItem.Rotation.Horizontal, rotation); } - + [TestMethod] public void GetOrientationFromExifItem_5() { var dir3 = new ExifIfd0Directory(); dir3.Set(ExifDirectoryBase.TagOrientation, 5); // "Left side, top (Mirror horizontal and rotate 270 CW)", - - var rotation = ReadMetaExif.GetOrientationFromExifItem(new List{dir3}); - Assert.AreEqual(FileIndexItem.Rotation.Horizontal,rotation); + + var rotation = ReadMetaExif.GetOrientationFromExifItem(new List { dir3 }); + Assert.AreEqual(FileIndexItem.Rotation.Horizontal, rotation); } - + [TestMethod] public void GetOrientationFromExifItem_6() { var dir3 = new ExifIfd0Directory(); dir3.Set(ExifDirectoryBase.TagOrientation, 6); // "Right side, top (Rotate 90 CW)", --6 - - var rotation = ReadMetaExif.GetOrientationFromExifItem(new List{dir3}); - Assert.AreEqual(FileIndexItem.Rotation.Rotate90Cw,rotation); + + var rotation = ReadMetaExif.GetOrientationFromExifItem(new List { dir3 }); + Assert.AreEqual(FileIndexItem.Rotation.Rotate90Cw, rotation); } - + [TestMethod] public void GetOrientationFromExifItem_7() { var dir3 = new ExifIfd0Directory(); dir3.Set(ExifDirectoryBase.TagOrientation, 7); // "Right side, bottom (Mirror horizontal and rotate 90 CW)", --7 - - var rotation = ReadMetaExif.GetOrientationFromExifItem(new List{dir3}); - Assert.AreEqual(FileIndexItem.Rotation.Horizontal,rotation); + + var rotation = ReadMetaExif.GetOrientationFromExifItem(new List { dir3 }); + Assert.AreEqual(FileIndexItem.Rotation.Horizontal, rotation); } - + [TestMethod] public void GetOrientationFromExifItem_8() { var dir3 = new ExifIfd0Directory(); dir3.Set(ExifDirectoryBase.TagOrientation, 8); // "Left side, bottom (Rotate 270 CW)") --8 - - var rotation = ReadMetaExif.GetOrientationFromExifItem(new List{dir3}); - Assert.AreEqual(FileIndexItem.Rotation.Rotate270Cw,rotation); + + var rotation = ReadMetaExif.GetOrientationFromExifItem(new List { dir3 }); + Assert.AreEqual(FileIndexItem.Rotation.Rotate270Cw, rotation); } - + [TestMethod] public void TestGetXmpGeoData() { // Arrange - - const string xmpData = "\n" + - "\n\n " + - "\n" + - " ALB\n \n\n" + - " \n" + - " 800796/527\n" + - " 0\n" + - " 42,27.7005N\n" + - " 19,52.86888E\n" + - " \n\n \n" + - " Valbone\n" + - " ShqipΓ«ri\n" + - " 2023-06-29T11:21:36\n" + - " Kukes County\n " + - "\n\n\n\n"; - + + const string xmpData = + "\n" + + "\n\n " + + "\n" + + " ALB\n \n\n" + + " \n" + + " 800796/527\n" + + " 0\n" + + " 42,27.7005N\n" + + " 19,52.86888E\n" + + " \n\n \n" + + " Valbone\n" + + " ShqipΓ«ri\n" + + " 2023-06-29T11:21:36\n" + + " Kukes County\n " + + "\n\n\n\n"; + var xmpMeta = XmpMetaFactory.ParseFromString(xmpData); - + var container = new List(); var dir2 = new XmpDirectory(); dir2.SetXmpMeta(xmpMeta); container.Add(dir2); - + const string propertyPath = "exif:GPSLatitude"; // Act @@ -825,36 +887,37 @@ public void TestGetXmpGeoData() // Assert Assert.AreEqual(42.461675, result, 0.0000000001); } - + [TestMethod] public void TestGetXmpGeoData2() { // Arrange - - const string xmpData = "\n" + - "\n\n " + - "\n" + - " ALB\n \n\n" + - " \n" + - " 800796/527\n" + - " 0\n" + - " 42,27.7005N\n" + - " 19,52.86888E\n" + - " \n\n \n" + - " Valbone\n" + - " ShqipΓ«ri\n" + - " 2023-06-29T11:21:36\n" + - " Kukes County\n " + - "\n\n\n\n"; - + + const string xmpData = + "\n" + + "\n\n " + + "\n" + + " ALB\n \n\n" + + " \n" + + " 800796/527\n" + + " 0\n" + + " 42,27.7005N\n" + + " 19,52.86888E\n" + + " \n\n \n" + + " Valbone\n" + + " ShqipΓ«ri\n" + + " 2023-06-29T11:21:36\n" + + " Kukes County\n " + + "\n\n\n\n"; + var xmpMeta = XmpMetaFactory.ParseFromString(xmpData); - + var container = new List(); var dir2 = new XmpDirectory(); dir2.SetXmpMeta(xmpMeta); container.Add(dir2); - + const string propertyPath = "exif:GPSLongitude"; // Act diff --git a/starsky/starskytest/starsky.foundation.search/ViewModels/SearchViewModelTest.cs b/starsky/starskytest/starsky.foundation.search/ViewModels/SearchViewModelTest.cs index f7eaeb669a..8d96dd0a46 100644 --- a/starsky/starskytest/starsky.foundation.search/ViewModels/SearchViewModelTest.cs +++ b/starsky/starskytest/starsky.foundation.search/ViewModels/SearchViewModelTest.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -12,26 +13,26 @@ public sealed class SearchViewModelTest [TestMethod] public void SearchViewModelTest_TestIfTrash() { - var test = new SearchViewModel {SearchQuery = TrashKeyword.TrashKeywordString}; - Assert.AreEqual(PageViewType.PageType.Trash.ToString(),test.PageType); + var test = new SearchViewModel { SearchQuery = TrashKeyword.TrashKeywordString }; + Assert.AreEqual(PageViewType.PageType.Trash.ToString(), test.PageType); } - + [TestMethod] public void SearchViewModelTest_Default() { var test = new SearchViewModel(); - Assert.AreEqual(PageViewType.PageType.Search.ToString(),test.PageType); + Assert.AreEqual(PageViewType.PageType.Search.ToString(), test.PageType); } [TestMethod] public void SetAddSearchInStringType_Null() { var model = new SearchViewModel(); - - model.GetType().GetProperty(nameof(model.SearchIn))?.SetValue(model, null,null); - + + model.GetType().GetProperty(nameof(model.SearchIn))?.SetValue(model, null, null); + model.SetAddSearchInStringType(null!); - + Assert.AreNotEqual(null, model.SearchIn); } @@ -42,16 +43,17 @@ public void SetAddSearch_searchForType_Null() Assert.AreNotEqual(null, model.SearchFor); } - + [TestMethod] public void SetAddSearch_searchForType_SetAddSearchFor_Null() { var model = new SearchViewModel { SearchForInternal = null }; - model.GetType().GetProperty(nameof(model.SearchForInternal))?.SetValue(model, null,null); + model.GetType().GetProperty(nameof(model.SearchForInternal)) + ?.SetValue(model, null, null); model.SetAddSearchFor(""); - + Assert.AreNotEqual(null, model.SearchFor); } @@ -62,31 +64,34 @@ public void SearchForOptions_Null() Assert.AreNotEqual(null, model.SearchForOptions); } - + [TestMethod] public void SearchForOptions_SetAddSearchForOptions_Null() { var model = new SearchViewModel { SearchForOptionsInternal = null }; - model.GetType().GetProperty(nameof(model.SearchForOptionsInternal))?.SetValue(model, null,null); + model.GetType().GetProperty(nameof(model.SearchForOptionsInternal)) + ?.SetValue(model, null, null); model.SetAddSearchForOptions("test"); - + Assert.AreNotEqual(null, model.SearchForOptions); } - + [TestMethod] public void SearchForOptions_SetAddSearchForOptions_DotComma() { var model = new SearchViewModel { SearchForOptionsInternal = null }; - model.GetType().GetProperty(nameof(model.SearchForOptionsInternal))?.SetValue(model, null,null); + model.GetType().GetProperty(nameof(model.SearchForOptionsInternal)) + ?.SetValue(model, null, null); model.SetAddSearchForOptions(";"); - - Assert.AreEqual(SearchViewModel.SearchForOptionType.Equal, model.SearchForOptions.LastOrDefault()); + + Assert.AreEqual(SearchViewModel.SearchForOptionType.Equal, + model.SearchForOptions.LastOrDefault()); } - + [TestMethod] public void SetAndOrOperator_amp_False() { @@ -99,44 +104,294 @@ public void SetAndOrOperator_amp_False() [TestMethod] public void SearchOperatorContinue_IgnoreNegativeValue() { + var model = new SearchViewModel { SearchOperatorOptionsInternal = new List() }; + var result = model.SearchOperatorContinue(-1, 1); + + Assert.IsTrue(result); + } + + [TestMethod] + public void SearchOperatorContinue_IgnoreOutOfRange() + { + var model = new SearchViewModel { SearchOperatorOptionsInternal = new List() }; + var result = model.SearchOperatorContinue(10, 1); + + Assert.IsTrue(result); + } + + [TestMethod] + public void SearchOperatorContinue_IgnoreOutOfRange2() + { + var model = new SearchViewModel { SearchOperatorOptionsInternal = new List() }; + var result = model.SearchOperatorContinue(0, 1); + + Assert.IsTrue(result); + } + + [TestMethod] + public void NarrowSearch_NoFileIndexItems() + { + var result = SearchViewModel.NarrowSearch(new SearchViewModel()); + Assert.AreEqual(0, result.SearchCount); + } + + [TestMethod] + public void SearchViewModel_ElapsedSeconds_Test() + { + var searchViewModel = new SearchViewModel { ElapsedSeconds = 0.0006 }; + Assert.AreEqual(true, searchViewModel.ElapsedSeconds <= 0.001); + } + + [TestMethod] + public void SearchViewModel_Offset_Test() + { + var searchViewModel = new SearchViewModel(); + Assert.AreEqual(0, Math.Floor(searchViewModel.Offset)); + } + + [TestMethod] + public void PropertySearchTest() + { + var property = new FileIndexItem { Tags = "q" }.GetType() + .GetProperty(nameof(FileIndexItem.Tags))!; + + // not a great test + var search = SearchViewModel.PropertySearch(new SearchViewModel { SearchFor = { "q" } }, + property, + "q", SearchViewModel.SearchForOptionType.Equal); + + Assert.AreEqual(0, search.CollectionsCount); + } + + [TestMethod] + public void PropertySearchStringType_DefaultCase_NullConditions1() + { + // Arrange + var model = new SearchViewModel { FileIndexItems = null }; + + var property = typeof(FileIndexItem).GetProperty("NotFound"); + Assert.IsNull(property); + + const string searchForQuery = "file"; + var searchType = SearchViewModel.SearchForOptionType.Equal; + + // Act + var result = + SearchViewModel.PropertySearchStringType(model, property!, searchForQuery, + searchType); + + // Assert + Assert.IsNotNull(result); + Assert.IsNull(result.FileIndexItems); + } + + [TestMethod] + public void PropertySearchStringType_DefaultCase_NullConditions2() + { + // Arrange + var model = new SearchViewModel { FileIndexItems = null }; + + var property = typeof(FileIndexItem).GetProperty(nameof(FileIndexItem.Tags)); + const string searchForQuery = "file"; + var searchType = SearchViewModel.SearchForOptionType.Equal; + + // Act + var result = + SearchViewModel.PropertySearchStringType(model, property!, searchForQuery, + searchType); + + // Assert + Assert.IsNotNull(result); + Assert.IsNull(result.FileIndexItems); + } + + [TestMethod] + public void PropertySearchStringType_DefaultCase_Found_Null() + { + // Arrange var model = new SearchViewModel { - SearchOperatorOptionsInternal = new List() + FileIndexItems = new List + { + new FileIndexItem("test") { LocationCity = null } + } }; - var result = model.SearchOperatorContinue(-1,1); - - Assert.IsTrue(result); + + var property = typeof(FileIndexItem).GetProperty(nameof(FileIndexItem.LocationCity)); + const string searchForQuery = "file"; + var searchType = SearchViewModel.SearchForOptionType.Equal; + + // Act + var result = + SearchViewModel.PropertySearchStringType(model, property!, searchForQuery, + searchType); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(0, result.FileIndexItems?.Count); } [TestMethod] - public void SearchOperatorContinue_IgnoreOutOfRange() + public void PropertySearchStringType_DefaultCase_Found_HappyFlow() { + // Arrange var model = new SearchViewModel { - SearchOperatorOptionsInternal = new List() + FileIndexItems = new List + { + new FileIndexItem("test") { LocationCity = "test" } + } }; - var result = model.SearchOperatorContinue(10,1); - - Assert.IsTrue(result); + + var property = typeof(FileIndexItem).GetProperty(nameof(FileIndexItem.LocationCity)); + const string searchForQuery = "test"; + var searchType = SearchViewModel.SearchForOptionType.Equal; + + // Act + var result = + SearchViewModel.PropertySearchStringType(model, property!, searchForQuery, + searchType); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(1, result.FileIndexItems?.Count); } - + [TestMethod] - public void SearchOperatorContinue_IgnoreOutOfRange2() + public void PropertySearchBoolType_FiltersItemsByBoolProperty() + { + // Arrange + var model = new SearchViewModel(); + model.FileIndexItems = new List + { + new FileIndexItem { IsDirectory = true }, + new FileIndexItem { IsDirectory = false }, + new FileIndexItem { IsDirectory = true }, + }; + var property = typeof(FileIndexItem).GetProperty("IsDirectory"); + var boolIsValue = true; + + // Act + var result = SearchViewModel.PropertySearchBoolType(model, property, boolIsValue); + + // Assert + Assert.AreEqual(2, result.FileIndexItems?.Count); + Assert.IsTrue(result.FileIndexItems?.Exists(item => item.IsDirectory == true)); + } + + [TestMethod] + public void PropertySearchBoolType_WithNullModel_ReturnsNullModel() + { + // Arrange + SearchViewModel? model = null; + var property = typeof(FileIndexItem).GetProperty("IsDirectory"); + const bool boolIsValue = true; + + // Act + var result = SearchViewModel.PropertySearchBoolType(model, property, boolIsValue); + + // Assert + Assert.IsNotNull(result); + } + + [TestMethod] + public void PropertySearchBoolType_WithNullFileIndexItems_ReturnsNullFileIndexItems() + { + // Arrange + var model = new SearchViewModel { FileIndexItems = null, }; + var property = typeof(FileIndexItem).GetProperty("IsDirectory"); + var boolIsValue = true; + + // Act + var result = SearchViewModel.PropertySearchBoolType(model, property, boolIsValue); + + // Assert + Assert.IsNull(result.FileIndexItems); + } + + [TestMethod] + public void PropertySearchBoolType_WithEmptyFileIndexItems_ReturnsEmptyFileIndexItems() + { + // Arrange + var model = new SearchViewModel { FileIndexItems = new List(), }; + var property = typeof(FileIndexItem).GetProperty("IsDirectory"); + var boolIsValue = true; + + // Act + var result = SearchViewModel.PropertySearchBoolType(model, property, boolIsValue); + + // Assert + Assert.IsNotNull(result.FileIndexItems); + Assert.AreEqual(0, result.FileIndexItems.Count); + } + + [TestMethod] + public void PropertySearchBoolType_WithInvalidProperty_ReturnsOriginalModel() { + // Arrange + var model = new SearchViewModel(); + model.FileIndexItems = new List + { + new FileIndexItem { IsDirectory = true }, + }; + var property = typeof(FileIndexItem).GetProperty("NonExistentProperty"); + var boolIsValue = true; + + // Act + var result = SearchViewModel.PropertySearchBoolType(model, property, boolIsValue); + + // Assert + Assert.AreEqual(model, result); + } + + [TestMethod] + public void PropertySearch_WithBoolPropertyAndValidBoolValue_ReturnsFilteredModel() + { + // Arrange var model = new SearchViewModel { - SearchOperatorOptionsInternal = new List() + FileIndexItems = new List + { + new FileIndexItem { IsDirectory = true }, + new FileIndexItem { IsDirectory = false }, + new FileIndexItem { IsDirectory = true }, + } }; - var result = model.SearchOperatorContinue(0,1); - - Assert.IsTrue(result); + var property = typeof(FileIndexItem).GetProperty("IsDirectory"); + const string searchForQuery = "true"; + const SearchViewModel.SearchForOptionType searchType = + SearchViewModel.SearchForOptionType.Equal; + + // Act + var result = + SearchViewModel.PropertySearch(model, property!, searchForQuery, searchType); + + // Assert + Assert.AreEqual(2, result.FileIndexItems?.Count); + Assert.IsTrue(result.FileIndexItems?.Exists(item => item.IsDirectory == true)); } [TestMethod] - public void NarrowSearch_NoFileIndexItems() + public void PropertySearch_WithBoolPropertyAndInvalidBoolValue_ReturnsOriginalModel() { - var result = SearchViewModel.NarrowSearch(new SearchViewModel()); - Assert.AreEqual(0, result.SearchCount); + // Arrange + var model = new SearchViewModel(); + model.FileIndexItems = new List + { + new FileIndexItem { IsDirectory = true }, + new FileIndexItem { IsDirectory = false }, + }; + var property = typeof(FileIndexItem).GetProperty("IsDirectory"); + const string searchForQuery = "invalid_bool_value"; // An invalid boolean string + const SearchViewModel.SearchForOptionType searchType = + SearchViewModel.SearchForOptionType.Equal; // You can set this as needed + + // Act + var result = + SearchViewModel.PropertySearch(model, property!, searchForQuery, searchType); + + // Assert + CollectionAssert.AreEqual(model.FileIndexItems, result.FileIndexItems); } } } diff --git a/starsky/starskytest/starsky.foundation.storage/Models/FolderOrFileModelTest.cs b/starsky/starskytest/starsky.foundation.storage/Models/FolderOrFileModelTest.cs new file mode 100644 index 0000000000..ba7fb41a8b --- /dev/null +++ b/starsky/starskytest/starsky.foundation.storage/Models/FolderOrFileModelTest.cs @@ -0,0 +1,18 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.storage.Models; + +namespace starskytest.starsky.foundation.storage.Models +{ + [TestClass] + public sealed class FolderOrFileModelTest + { + [TestMethod] + public void FolderOrFileModelFolderOrFileTypeListTest() + { + const FolderOrFileModel.FolderOrFileTypeList searchType = + FolderOrFileModel.FolderOrFileTypeList.Folder; + var folderOrFileModel = new FolderOrFileModel { IsFolderOrFile = searchType }; + Assert.AreEqual(searchType, folderOrFileModel.IsFolderOrFile); + } + } +} diff --git a/starsky/starskytest/starsky.foundation.writemeta/Helpers/EnumHelperTest.cs b/starsky/starskytest/starsky.foundation.writemeta/Helpers/EnumHelperTest.cs new file mode 100644 index 0000000000..f5af9fb725 --- /dev/null +++ b/starsky/starskytest/starsky.foundation.writemeta/Helpers/EnumHelperTest.cs @@ -0,0 +1,111 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.writemeta.Helpers; + +namespace starskytest.starsky.foundation.writemeta.Helpers; + +[TestClass] +public class EnumHelperTest +{ + public enum TestValue + { + [Display(Name = "Test One")] Value1, + + [Display(Name = "Test Two")] Value2, + + Value3, + + [Display(Name = null)] Value4 + } + + [TestMethod] + public void Test_GetDisplayName_ReturnsDisplayName_ForEnumWithDisplayName() + { + // Arrange + var enumValue = TestValue.Value1; + + // Act + var result = EnumHelper.GetDisplayName(enumValue); + + // Assert + Assert.AreEqual("Test One", result); + } + + [TestMethod] + public void Test_GetDisplayName_ReturnsEnumValue_ForEnumWithoutDisplayName() + { + // Arrange + var enumValue = TestValue.Value3; + + // Act + var result = EnumHelper.GetDisplayName(enumValue); + + // Assert + Assert.AreEqual(null, result); + } + + [TestMethod] + public void Test_GetDisplayName_ReturnsEmptyString_ForEnumWithNullDisplayName() + { + // Arrange + var enumValue = TestValue.Value4; + + // Act + var result = EnumHelper.GetDisplayName(enumValue); + + // Assert + Assert.AreEqual(null, result); + } + + [TestMethod] + public void Test_GetDisplayName_ReturnsEmptyString_ForNullEnum() + { + // Arrange + TestValue? enumValue = null; + + // Act + var result = EnumHelper.GetDisplayName(enumValue!); + + // Assert + Assert.AreEqual(null, result); + } + + [TestMethod] + public void Test_GetDisplayName_ReturnsDisplayName_ForNullableEnumWithDisplayName() + { + // Arrange + TestValue? enumValue = TestValue.Value1; + + // Act + var result = EnumHelper.GetDisplayName(enumValue); + + // Assert + Assert.AreEqual("Test One", result); + } + + [TestMethod] + public void Test_GetDisplayName_ReturnsEnumValue_ForNullableEnumWithoutDisplayName() + { + // Arrange + TestValue? enumValue = TestValue.Value3; + + // Act + var result = EnumHelper.GetDisplayName(enumValue); + + // Assert + Assert.AreEqual(null, result); + } + + [TestMethod] + public void Test_GetDisplayName_ReturnsEmptyString_ForNullableEnumWithNullDisplayName() + { + // Arrange + TestValue? enumValue = TestValue.Value4; + + // Act + var result = EnumHelper.GetDisplayName(enumValue); + + // Assert + Assert.AreEqual(null, result); + } +} diff --git a/starsky/starskytest/starsky.foundation.writemeta/Helpers/ExifToolCmdHelperTest.cs b/starsky/starskytest/starsky.foundation.writemeta/Helpers/ExifToolCmdHelperTest.cs index b1538e93ea..4791852eb2 100644 --- a/starsky/starskytest/starsky.foundation.writemeta/Helpers/ExifToolCmdHelperTest.cs +++ b/starsky/starskytest/starsky.foundation.writemeta/Helpers/ExifToolCmdHelperTest.cs @@ -7,7 +7,6 @@ using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Models; using starskytest.FakeMocks; -using starskytest.Models; using ExifToolCmdHelper = starsky.foundation.writemeta.Helpers.ExifToolCmdHelper; namespace starskytest.starsky.foundation.writemeta.Helpers @@ -42,7 +41,8 @@ public void ExifToolCmdHelper_UpdateTest() Orientation = FileIndexItem.Rotation.Rotate90Cw, DateTime = DateTime.Now, }; - var comparedNames = new List{ + var comparedNames = new List + { nameof(FileIndexItem.Tags).ToLowerInvariant(), nameof(FileIndexItem.Description).ToLowerInvariant(), nameof(FileIndexItem.Latitude).ToLowerInvariant(), @@ -57,231 +57,240 @@ public void ExifToolCmdHelper_UpdateTest() nameof(FileIndexItem.Orientation).ToLowerInvariant(), nameof(FileIndexItem.DateTime).ToLowerInvariant(), }; - - var inputSubPaths = new List - { - "/test.jpg" - }; - var storage = new FakeIStorage(new List{"/"},new List{"/test.jpg"},new List()); - - var fakeExifTool = new FakeExifTool(storage,_appSettings); - var helperResult = new ExifToolCmdHelper(fakeExifTool, storage,storage , - new FakeReadMeta(), new FakeIThumbnailQuery()).Update(updateModel, inputSubPaths, comparedNames); - - Assert.AreEqual(true,helperResult.Contains(updateModel.Tags)); - Assert.AreEqual(true,helperResult.Contains(updateModel.Description)); - Assert.AreEqual(true,helperResult.Contains(updateModel.Latitude.ToString(CultureInfo.InvariantCulture))); - Assert.AreEqual(true,helperResult.Contains(updateModel.Longitude.ToString(CultureInfo.InvariantCulture))); - Assert.AreEqual(true,helperResult.Contains(updateModel.LocationAltitude.ToString(CultureInfo.InvariantCulture))); - Assert.AreEqual(true,helperResult.Contains(updateModel.LocationCity)); - Assert.AreEqual(true,helperResult.Contains(updateModel.LocationState)); - Assert.AreEqual(true,helperResult.Contains(updateModel.LocationCountry)); - Assert.AreEqual(true,helperResult.Contains(updateModel.LocationCountryCode)); - Assert.AreEqual(true,helperResult.Contains(updateModel.Title)); + + var inputSubPaths = new List { "/test.jpg" }; + var storage = new FakeIStorage(new List { "/" }, + new List { "/test.jpg" }, new List()); + + var fakeExifTool = new FakeExifTool(storage, _appSettings); + var helperResult = new ExifToolCmdHelper(fakeExifTool, storage, storage, + new FakeReadMeta(), new FakeIThumbnailQuery()) + .Update(updateModel, inputSubPaths, comparedNames); + + Assert.AreEqual(true, helperResult.Contains(updateModel.Tags)); + Assert.AreEqual(true, helperResult.Contains(updateModel.Description)); + Assert.AreEqual(true, + helperResult.Contains(updateModel.Latitude.ToString(CultureInfo.InvariantCulture))); + Assert.AreEqual(true, + helperResult.Contains( + updateModel.Longitude.ToString(CultureInfo.InvariantCulture))); + Assert.AreEqual(true, + helperResult.Contains( + updateModel.LocationAltitude.ToString(CultureInfo.InvariantCulture))); + Assert.AreEqual(true, helperResult.Contains(updateModel.LocationCity)); + Assert.AreEqual(true, helperResult.Contains(updateModel.LocationState)); + Assert.AreEqual(true, helperResult.Contains(updateModel.LocationCountry)); + Assert.AreEqual(true, helperResult.Contains(updateModel.LocationCountryCode)); + Assert.AreEqual(true, helperResult.Contains(updateModel.Title)); } [TestMethod] public void ExifToolCmdHelper_Update_UpdateLocationAltitudeCommandTest() { - var updateModel = new FileIndexItem + var updateModel = new FileIndexItem { LocationAltitude = -41, }; + var comparedNames = new List { - LocationAltitude = -41, - }; - var comparedNames = new List{ nameof(FileIndexItem.LocationAltitude).ToLowerInvariant(), }; - - var folderPaths = new List{"/"}; - var inputSubPaths = new List{"/test.jpg"}; + var folderPaths = new List { "/" }; + + var inputSubPaths = new List { "/test.jpg" }; var storage = new FakeIStorage(folderPaths, inputSubPaths); - var fakeExifTool = new FakeExifTool(storage,_appSettings); + var fakeExifTool = new FakeExifTool(storage, _appSettings); - var helperResult = new ExifToolCmdHelper(fakeExifTool, - storage,storage, - new FakeReadMeta(), new FakeIThumbnailQuery()).Update(updateModel, inputSubPaths, comparedNames); - - Assert.AreEqual(true,helperResult.Contains("-GPSAltitude=\"-41")); - Assert.AreEqual(true,helperResult.Contains("gpsaltituderef#=\"1")); + var helperResult = new ExifToolCmdHelper(fakeExifTool, + storage, storage, + new FakeReadMeta(), new FakeIThumbnailQuery()) + .Update(updateModel, inputSubPaths, comparedNames); + Assert.AreEqual(true, helperResult.Contains("-GPSAltitude=\"-41")); + Assert.AreEqual(true, helperResult.Contains("gpsaltituderef#=\"1")); } [TestMethod] public async Task CreateXmpFileIsNotExist_NotCreateFile_jpg() { - var updateModel = new FileIndexItem - { - LocationAltitude = -41, - }; - var folderPaths = new List{"/"}; + var updateModel = new FileIndexItem { LocationAltitude = -41, }; + var folderPaths = new List { "/" }; - var inputSubPaths = new List{"/test.jpg"}; + var inputSubPaths = new List { "/test.jpg" }; var storage = new FakeIStorage(folderPaths, inputSubPaths); - var fakeExifTool = new FakeExifTool(storage,_appSettings); - await new ExifToolCmdHelper(fakeExifTool, - storage,storage, - new FakeReadMeta(), new FakeIThumbnailQuery()).CreateXmpFileIsNotExist(updateModel, inputSubPaths); + var fakeExifTool = new FakeExifTool(storage, _appSettings); + await new ExifToolCmdHelper(fakeExifTool, + storage, storage, + new FakeReadMeta(), new FakeIThumbnailQuery()) + .CreateXmpFileIsNotExist(updateModel, inputSubPaths); Assert.IsFalse(storage.ExistFile("/test.xmp")); } - - [TestMethod] - public async Task CreateXmpFileIsNotExist_CreateFile_dng() - { - var updateModel = new FileIndexItem - { - LocationAltitude = -41, - }; - var folderPaths = new List{"/"}; - - var inputSubPaths = new List{"/test.dng"}; - - var storage = - new FakeIStorage(folderPaths, inputSubPaths); - var fakeExifTool = new FakeExifTool(storage,_appSettings); - await new ExifToolCmdHelper(fakeExifTool, - storage,storage, - new FakeReadMeta(), new FakeIThumbnailQuery()).CreateXmpFileIsNotExist(updateModel, inputSubPaths); - - Assert.IsTrue(storage.ExistFile("/test.xmp")); - } - - [TestMethod] - public async Task UpdateAsync_ShouldUpdate_SkipFileHash() - { - var updateModel = new FileIndexItem + + [TestMethod] + public async Task CreateXmpFileIsNotExist_CreateFile_dng() + { + var updateModel = new FileIndexItem { LocationAltitude = -41, }; + var folderPaths = new List { "/" }; + + var inputSubPaths = new List { "/test.dng" }; + + var storage = + new FakeIStorage(folderPaths, inputSubPaths); + var fakeExifTool = new FakeExifTool(storage, _appSettings); + await new ExifToolCmdHelper(fakeExifTool, + storage, storage, + new FakeReadMeta(), new FakeIThumbnailQuery()) + .CreateXmpFileIsNotExist(updateModel, inputSubPaths); + + Assert.IsTrue(storage.ExistFile("/test.xmp")); + } + + [TestMethod] + public async Task UpdateAsync_ShouldUpdate_SkipFileHash() + { + var updateModel = new FileIndexItem { Tags = "tags", Description = "Description", }; + var comparedNames = new List + { + nameof(FileIndexItem.Tags).ToLowerInvariant(), + nameof(FileIndexItem.Description).ToLowerInvariant(), + }; + + var storage = new FakeIStorage(new List { "/" }, + new List { "/test.jpg" }, new List()); + + var fakeExifTool = new FakeExifTool(storage, _appSettings); + var helperResult = ( await new ExifToolCmdHelper(fakeExifTool, storage, storage, + new FakeReadMeta(), new FakeIThumbnailQuery()) + .UpdateAsync(updateModel, comparedNames) ); + + Assert.IsTrue(helperResult.Item1.Contains("tags")); + Assert.IsTrue(helperResult.Item1.Contains("Description")); + } + + [TestMethod] + public async Task UpdateAsync_ShouldUpdate_IncludeFileHash() + { + var updateModel = new FileIndexItem { Tags = "tags", Description = "Description", + FileHash = "_hash_test" // < - - - - include here }; - var comparedNames = new List{ + var comparedNames = new List + { nameof(FileIndexItem.Tags).ToLowerInvariant(), nameof(FileIndexItem.Description).ToLowerInvariant(), }; - var storage = new FakeIStorage(new List{"/"},new List{"/test.jpg"},new List()); + var storage = new FakeIStorage(new List { "/" }, + new List { "/test.jpg" }, new List()); + + var fakeExifTool = new FakeExifTool(storage, _appSettings); + var helperResult = ( await new ExifToolCmdHelper(fakeExifTool, storage, storage, + new FakeReadMeta(), new FakeIThumbnailQuery()) + .UpdateAsync(updateModel, comparedNames) ); - var fakeExifTool = new FakeExifTool(storage,_appSettings); - var helperResult = (await new ExifToolCmdHelper(fakeExifTool, storage,storage , - new FakeReadMeta(), new FakeIThumbnailQuery()).UpdateAsync(updateModel, comparedNames)); - Assert.IsTrue(helperResult.Item1.Contains("tags")); Assert.IsTrue(helperResult.Item1.Contains("Description")); - } - - [TestMethod] - public async Task UpdateAsync_ShouldUpdate_IncludeFileHash() - { - var updateModel = new FileIndexItem - { - Tags = "tags", - Description = "Description", - FileHash = "_hash_test" // < - - - - include here - }; - var comparedNames = new List{ - nameof(FileIndexItem.Tags).ToLowerInvariant(), - nameof(FileIndexItem.Description).ToLowerInvariant(), - }; - - var storage = new FakeIStorage(new List{"/"},new List{"/test.jpg"},new List()); - - var fakeExifTool = new FakeExifTool(storage,_appSettings); - var helperResult = (await new ExifToolCmdHelper(fakeExifTool, storage,storage , - new FakeReadMeta(), new FakeIThumbnailQuery()).UpdateAsync(updateModel, comparedNames)); - - Assert.IsTrue(helperResult.Item1.Contains("tags")); - Assert.IsTrue(helperResult.Item1.Contains("Description")); - } - - - [TestMethod] - public void ExifToolCommandLineArgsImageStabilisation() - { - var updateModel = new FileIndexItem - { - ImageStabilisation = ImageStabilisationType.On // < - - - - include here - }; - var comparedNames = new List{ - nameof(FileIndexItem.ImageStabilisation).ToLowerInvariant(), - }; - - var result = ExifToolCmdHelper.ExifToolCommandLineArgs(updateModel, - comparedNames, true); - - Assert.AreEqual("-json -overwrite_original -ImageStabilization=\"On\"",result); - } - - [TestMethod] - public void ExifToolCommandLineArgsImageStabilisationUnknown() - { - var updateModel = new FileIndexItem - { - ImageStabilisation = ImageStabilisationType.Unknown // < - - - - include here - }; - var comparedNames = new List{ - nameof(FileIndexItem.ImageStabilisation).ToLowerInvariant(), - }; - - var result = ExifToolCmdHelper.ExifToolCommandLineArgs(updateModel, - comparedNames, true); - - Assert.AreEqual(string.Empty,result); - } - - [TestMethod] - public void ExifToolCommandLineArgs_LocationCountryCode() - { - var updateModel = new FileIndexItem - { - LocationCountryCode = "NLD" // < - - - - include here - }; - var comparedNames = new List{ - nameof(FileIndexItem.LocationCountryCode).ToLowerInvariant(), - }; - - var result = ExifToolCmdHelper.ExifToolCommandLineArgs(updateModel, - comparedNames, true); - - Assert.AreEqual("-json -overwrite_original -Country-PrimaryLocationCode=\"NLD\" -XMP:CountryCode=\"NLD\"",result); - } - - - [TestMethod] - public void UpdateSoftwareCommand_True() - { - var updateModel = new FileIndexItem - { - Software = "Test" // < - - - - include here - }; - var comparedNames = new List{ - nameof(FileIndexItem.Software).ToLowerInvariant(), - }; - - var result = ExifToolCmdHelper.UpdateSoftwareCommand(string.Empty, comparedNames, updateModel, true); - - Assert.AreEqual(" -Software=\"Test\" -CreatorTool=\"Test\" " + - "-HistorySoftwareAgent=\"Test\" -HistoryParameters=\"\" -PMVersion=\"\" ",result); - } - - [TestMethod] - public void UpdateSoftwareCommand_False() - { - var updateModel = new FileIndexItem - { - Software = "Test" // < - - - - include here - }; - var comparedNames = new List{ - nameof(FileIndexItem.Software).ToLowerInvariant(), - }; - - var result = ExifToolCmdHelper.UpdateSoftwareCommand(string.Empty, comparedNames, updateModel, false); - - Assert.AreEqual(" -Software=\"Starsky\" -CreatorTool=\"Starsky\" " + - "-HistorySoftwareAgent=\"Starsky\" -HistoryParameters=\"\" -PMVersion=\"\" ",result); - } + } + + + [TestMethod] + public void ExifToolCommandLineArgsImageStabilisation() + { + var updateModel = new FileIndexItem + { + ImageStabilisation = ImageStabilisationType.On // < - - - - include here + }; + var comparedNames = new List + { + nameof(FileIndexItem.ImageStabilisation).ToLowerInvariant(), + }; + + var result = ExifToolCmdHelper.ExifToolCommandLineArgs(updateModel, + comparedNames, true); + + Assert.AreEqual("-json -overwrite_original -ImageStabilization=\"On\"", result); + } + + [TestMethod] + public void ExifToolCommandLineArgsImageStabilisationUnknown() + { + var updateModel = new FileIndexItem + { + ImageStabilisation = ImageStabilisationType.Unknown // < - - - - include here + }; + var comparedNames = new List + { + nameof(FileIndexItem.ImageStabilisation).ToLowerInvariant(), + }; + + var result = ExifToolCmdHelper.ExifToolCommandLineArgs(updateModel, + comparedNames, true); + + Assert.AreEqual(string.Empty, result); + } + + [TestMethod] + public void ExifToolCommandLineArgs_LocationCountryCode() + { + var updateModel = new FileIndexItem + { + LocationCountryCode = "NLD" // < - - - - include here + }; + var comparedNames = new List + { + nameof(FileIndexItem.LocationCountryCode).ToLowerInvariant(), + }; + + var result = ExifToolCmdHelper.ExifToolCommandLineArgs(updateModel, + comparedNames, true); + + Assert.AreEqual( + "-json -overwrite_original -Country-PrimaryLocationCode=\"NLD\" -XMP:CountryCode=\"NLD\"", + result); + } + + + [TestMethod] + public void UpdateSoftwareCommand_True() + { + var updateModel = new FileIndexItem + { + Software = "Test" // < - - - - include here + }; + var comparedNames = + new List { nameof(FileIndexItem.Software).ToLowerInvariant(), }; + + var result = + ExifToolCmdHelper.UpdateSoftwareCommand(string.Empty, comparedNames, updateModel, + true); + + Assert.AreEqual(" -Software=\"Test\" -CreatorTool=\"Test\" " + + "-HistorySoftwareAgent=\"Test\" -HistoryParameters=\"\" -PMVersion=\"\" ", + result); + } + + [TestMethod] + public void UpdateSoftwareCommand_False() + { + var updateModel = new FileIndexItem + { + Software = "Test" // < - - - - include here + }; + var comparedNames = + new List { nameof(FileIndexItem.Software).ToLowerInvariant(), }; + + var result = + ExifToolCmdHelper.UpdateSoftwareCommand(string.Empty, comparedNames, updateModel, + false); + + Assert.AreEqual(" -Software=\"Starsky\" -CreatorTool=\"Starsky\" " + + "-HistorySoftwareAgent=\"Starsky\" -HistoryParameters=\"\" -PMVersion=\"\" ", + result); + } } } diff --git a/starsky/starskytest/starsky.foundation.writemeta/Services/ExifCopyTest.cs b/starsky/starskytest/starsky.foundation.writemeta/Services/ExifCopyTest.cs index 7d463d3e54..0ba7c49fcf 100644 --- a/starsky/starskytest/starsky.foundation.writemeta/Services/ExifCopyTest.cs +++ b/starsky/starskytest/starsky.foundation.writemeta/Services/ExifCopyTest.cs @@ -7,7 +7,6 @@ using starsky.foundation.storage.Helpers; using starsky.foundation.writemeta.Services; using starskytest.FakeMocks; -using starskytest.Models; namespace starskytest.starsky.foundation.writemeta.Services { @@ -21,87 +20,89 @@ public ExifCopyTest() // get the service _appSettings = new AppSettings(); } - + [TestMethod] public async Task ExifToolCmdHelper_CopyExifPublish() { - var folderPaths = new List{"/"}; - var inputSubPaths = new List{"/test.jpg"}; + var folderPaths = new List { "/" }; + var inputSubPaths = new List { "/test.jpg" }; var storage = - new FakeIStorage(folderPaths, inputSubPaths, - new List{FakeCreateAn.CreateAnImage.Bytes.ToArray()}); - + new FakeIStorage(folderPaths, inputSubPaths, + new List { FakeCreateAn.CreateAnImage.Bytes.ToArray() }); + var fakeReadMeta = new ReadMeta(storage, _appSettings, null, new FakeIWebLogger()); - var fakeExifTool = new FakeExifTool(storage,_appSettings); - var helperResult = await new ExifCopy(storage, storage, fakeExifTool, + var fakeExifTool = new FakeExifTool(storage, _appSettings); + var helperResult = await new ExifCopy(storage, storage, fakeExifTool, fakeReadMeta, new FakeIThumbnailQuery()).CopyExifPublish("/test.jpg", "/test2"); - Assert.AreEqual(true,helperResult.Contains("HistorySoftwareAgent")); + Assert.AreEqual(true, helperResult.Contains("HistorySoftwareAgent")); } [TestMethod] public async Task ExifToolCmdHelper_XmpSync() { - var folderPaths = new List{"/"}; - var inputSubPaths = new List{"/test.dng"}; + var folderPaths = new List { "/" }; + var inputSubPaths = new List { "/test.dng" }; var storage = - new FakeIStorage(folderPaths, inputSubPaths, - new List{FakeCreateAn.CreateAnImage.Bytes.ToArray()}); + new FakeIStorage(folderPaths, inputSubPaths, + new List { FakeCreateAn.CreateAnImage.Bytes.ToArray() }); - var fakeReadMeta = new ReadMeta(storage, _appSettings, + var fakeReadMeta = new ReadMeta(storage, _appSettings, null, new FakeIWebLogger()); - var fakeExifTool = new FakeExifTool(storage,_appSettings); - var helperResult = await new ExifCopy(storage, - storage, fakeExifTool, fakeReadMeta, new FakeIThumbnailQuery()).XmpSync("/test.dng"); - Assert.AreEqual("/test.xmp",helperResult); - + var fakeExifTool = new FakeExifTool(storage, _appSettings); + var helperResult = await new ExifCopy(storage, + storage, fakeExifTool, fakeReadMeta, + new FakeIThumbnailQuery()).XmpSync("/test.dng"); + Assert.AreEqual("/test.xmp", helperResult); } [TestMethod] public async Task ExifToolCmdHelper_XmpCreate() { - var folderPaths = new List{"/"}; - var inputSubPaths = new List{"/test.dng"}; + var folderPaths = new List { "/" }; + var inputSubPaths = new List { "/test.dng" }; var storage = - new FakeIStorage(folderPaths, inputSubPaths, - new List{FakeCreateAn.CreateAnImage.Bytes.ToArray()}); + new FakeIStorage(folderPaths, inputSubPaths, + new List { FakeCreateAn.CreateAnImage.Bytes.ToArray() }); var fakeReadMeta = new ReadMeta(storage, _appSettings, null, new FakeIWebLogger()); - var fakeExifTool = new FakeExifTool(storage,_appSettings); - + var fakeExifTool = new FakeExifTool(storage, _appSettings); + new ExifCopy(storage, storage, fakeExifTool, fakeReadMeta, new FakeIThumbnailQuery()) .XmpCreate("/test.xmp"); - var result = await StreamToStringHelper.StreamToStringAsync(storage.ReadStream("/test.xmp")); + var result = + await StreamToStringHelper.StreamToStringAsync(storage.ReadStream("/test.xmp")); Assert.AreEqual("\n" + - "\n\n",result); + "\n\n", + result); } [TestMethod] public async Task ExifToolCmdHelper_TestForFakeExifToolInjection() { - var folderPaths = new List{"/"}; - var inputSubPaths = new List{"/test.dng"}; - + var folderPaths = new List { "/" }; + var inputSubPaths = new List { "/test.dng" }; + var storage = - new FakeIStorage(folderPaths, inputSubPaths, - new List{FakeCreateAn.CreateAnImage.Bytes.ToArray()}); - + new FakeIStorage(folderPaths, inputSubPaths, + new List { FakeCreateAn.CreateAnImage.Bytes.ToArray() }); + var readMeta = new ReadMeta(storage, _appSettings, null, new FakeIWebLogger()); - var fakeExifTool = new FakeExifTool(storage,_appSettings); + var fakeExifTool = new FakeExifTool(storage, _appSettings); await new ExifCopy(storage, storage, fakeExifTool, readMeta, new FakeIThumbnailQuery()) .XmpSync("/test.dng"); - - Assert.AreEqual(true,storage.ExistFile("/test.xmp")); + + Assert.AreEqual(true, storage.ExistFile("/test.xmp")); var xmpContentReadStream = storage.ReadStream("/test.xmp"); var xmpContent = await StreamToStringHelper.StreamToStringAsync(xmpContentReadStream); - - // Those values are injected by fakeExifTool - Assert.AreEqual(true,xmpContent.Contains("")); - Assert.AreEqual(true,xmpContent.Contains("test")); + // Those values are injected by fakeExifTool + Assert.AreEqual(true, + xmpContent.Contains( + "")); + Assert.AreEqual(true, xmpContent.Contains("test")); } - } } diff --git a/starsky/starskytest/Helpers/FilenameHelpersTest.cs b/starsky/starskytest/starsky.project.web/Helpers/FilenameHelpersTest.cs similarity index 98% rename from starsky/starskytest/Helpers/FilenameHelpersTest.cs rename to starsky/starskytest/starsky.project.web/Helpers/FilenameHelpersTest.cs index 2d4b3a6a49..15988ff23f 100644 --- a/starsky/starskytest/Helpers/FilenameHelpersTest.cs +++ b/starsky/starskytest/starsky.project.web/Helpers/FilenameHelpersTest.cs @@ -1,7 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.foundation.platform.Helpers; -namespace starskytest.Helpers +namespace starskytest.starsky.project.web.Helpers { [TestClass] public sealed class FilenameHelpersTest diff --git a/starsky/starskytest/starsky.project.web/Helpers/MimeHelperTest.cs b/starsky/starskytest/starsky.project.web/Helpers/MimeHelperTest.cs new file mode 100644 index 0000000000..64ea21e9ed --- /dev/null +++ b/starsky/starskytest/starsky.project.web/Helpers/MimeHelperTest.cs @@ -0,0 +1,48 @@ +ο»Ώusing Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.project.web.Helpers; + +namespace starskytest.starsky.project.web.Helpers +{ + [TestClass] + public sealed class MimeHelperTest + { + [TestMethod] + public void GetMimeTypeByFileNameTestUnknown() + { + Assert.AreEqual("application/octet-stream", + MimeHelper.GetMimeTypeByFileName("test.unknown")); + } + + [TestMethod] + public void GetMimeTypeByFileNameTestJpg() + { + Assert.AreEqual("image/jpeg", MimeHelper.GetMimeTypeByFileName("test.jpg")); + } + + [TestMethod] + public void GetMimeTypeByFileNameTestJpeg() + { + Assert.AreEqual("image/jpeg", MimeHelper.GetMimeTypeByFileName("test.jpeg")); + } + + [TestMethod] + public void GetMimeTypeByExtensionTest_NoExtension() + { + Assert.AreEqual("application/octet-stream", + MimeHelper.GetMimeTypeByFileName(string.Empty)); + } + + [TestMethod] + public void GetMimeType_NoExtension() + { + Assert.AreEqual("application/octet-stream", MimeHelper.GetMimeType(string.Empty)); + } + + + [TestMethod] + public void GetMimeType_Jpeg() + { + Assert.AreEqual("image/jpeg", MimeHelper.GetMimeType("jpg")); + } + } +} diff --git a/starsky/starskytest/starsky.project.web/Helpers/PortProgramHelperTest.cs b/starsky/starskytest/starsky.project.web/Helpers/PortProgramHelperTest.cs new file mode 100644 index 0000000000..43ba3a89fa --- /dev/null +++ b/starsky/starskytest/starsky.project.web/Helpers/PortProgramHelperTest.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.platform.Models; +using starsky.foundation.storage.Helpers; +using starsky.foundation.storage.Storage; +using starsky.project.web.Helpers; + +namespace starskytest.starsky.project.web.Helpers; + +[TestClass] +public class PortProgramHelperTest +{ + private readonly string? _prePort; + private readonly string? _preAspNetUrls; + + public PortProgramHelperTest() + { + _prePort = Environment.GetEnvironmentVariable("PORT"); + _preAspNetUrls = Environment.GetEnvironmentVariable("ASPNETCORE_URLS"); + } + + [TestMethod] + public void SetEnvPortAspNetUrls_ShouldSet() + { + Environment.SetEnvironmentVariable("PORT", "8000"); + Environment.SetEnvironmentVariable("ASPNETCORE_URLS", ""); + + PortProgramHelper.SetEnvPortAspNetUrls(new List()); + + Assert.AreEqual("http://*:8000", Environment.GetEnvironmentVariable("ASPNETCORE_URLS")); + + Environment.SetEnvironmentVariable("PORT", _prePort); + Environment.SetEnvironmentVariable("ASPNETCORE_URLS", _preAspNetUrls); + } + + [TestMethod] + public async Task SetEnvPortAspNetUrlsAndSetDefault_ShouldSet() + { + Environment.SetEnvironmentVariable("PORT", "8000"); + Environment.SetEnvironmentVariable("ASPNETCORE_URLS", ""); + + await PortProgramHelper.SetEnvPortAspNetUrlsAndSetDefault(Array.Empty(), + string.Empty); + Assert.AreEqual("http://*:8000", Environment.GetEnvironmentVariable("ASPNETCORE_URLS")); + + Environment.SetEnvironmentVariable("PORT", _prePort); + Environment.SetEnvironmentVariable("ASPNETCORE_URLS", _preAspNetUrls); + } + + [TestMethod] + public void SetEnvPortAspNetUrls_ShouldIgnore() + { + Environment.SetEnvironmentVariable("PORT", ""); + Environment.SetEnvironmentVariable("ASPNETCORE_URLS", ""); + + PortProgramHelper.SetEnvPortAspNetUrls(new List()); + Assert.AreEqual(null, Environment.GetEnvironmentVariable("ASPNETCORE_URLS")); + + Environment.SetEnvironmentVariable("PORT", _prePort); + Environment.SetEnvironmentVariable("ASPNETCORE_URLS", _preAspNetUrls); + } + + [TestMethod] + public async Task SetEnvPortAspNetUrlsAndSetDefault_ShouldIgnore_DueAppSettingsFile1() + { + Environment.SetEnvironmentVariable("PORT", ""); + Environment.SetEnvironmentVariable("ASPNETCORE_URLS", ""); + + var appSettingsPath = + Path.Combine(new AppSettings().BaseDirectoryProject, "appsettings-222.json"); + var stream = StringToStreamHelper.StringToStream( + "{ \"Kestrel\": {\n \"Endpoints\": {\n " + + " \"Https\": {\n \"Url\": \"https://*:8001\"\n },\n \"Http\": {\n " + + " \"Url\": \"http://*:8000\"\n }\n }\n }\n }"); + await new StorageHostFullPathFilesystem().WriteStreamAsync(stream, appSettingsPath); + + await PortProgramHelper.SetEnvPortAspNetUrlsAndSetDefault(Array.Empty(), + appSettingsPath); + + Assert.AreEqual(null, Environment.GetEnvironmentVariable("ASPNETCORE_URLS")); + + Environment.SetEnvironmentVariable("PORT", _prePort); + Environment.SetEnvironmentVariable("ASPNETCORE_URLS", _preAspNetUrls); + + // remove afterwards + new StorageHostFullPathFilesystem().FileDelete(appSettingsPath); + } + + + [TestMethod] + public async Task SkipForAppSettingsJsonFile_ShouldIgnore_DueAppSettingsFile() + { + Environment.SetEnvironmentVariable("PORT", ""); + Environment.SetEnvironmentVariable("ASPNETCORE_URLS", ""); + + var appSettingsPath = + Path.Combine(new AppSettings().BaseDirectoryProject, "appsettings-111.json"); + var stream = StringToStreamHelper.StringToStream( + "{ \"Kestrel\": {\n \"Endpoints\": {\n " + + " \"Https\": {\n \"Url\": \"https://*:8001\"\n },\n \"Http\": {\n " + + " \"Url\": \"http://*:8000\"\n }\n }\n }\n }"); + await new StorageHostFullPathFilesystem().WriteStreamAsync(stream, appSettingsPath); + + var result = await PortProgramHelper.SkipForAppSettingsJsonFile(appSettingsPath); + + Assert.AreEqual(true, result); + Assert.AreEqual(null, Environment.GetEnvironmentVariable("ASPNETCORE_URLS")); + + Environment.SetEnvironmentVariable("PORT", _prePort); + Environment.SetEnvironmentVariable("ASPNETCORE_URLS", _preAspNetUrls); + + // remove afterwards + new StorageHostFullPathFilesystem().FileDelete(appSettingsPath); + } + + [TestMethod] + public async Task SkipForAppSettingsJsonFile_ShouldIgnore_DueAppSettingsFile2() + { + Environment.SetEnvironmentVariable("PORT", ""); + Environment.SetEnvironmentVariable("ASPNETCORE_URLS", ""); + + var appSettingsPath = + Path.Combine(new AppSettings().BaseDirectoryProject, "appsettings-333.json"); + var stream = StringToStreamHelper.StringToStream( + "{ \"Kestrel\": {\n \"Endpoints\": {\n " + + " \"Https\": {\n \"Url\": \"https://*:8001\"\n }\n " + + "\n }\n }\n }"); + await new StorageHostFullPathFilesystem().WriteStreamAsync(stream, appSettingsPath); + + var result = await PortProgramHelper.SkipForAppSettingsJsonFile(appSettingsPath); + + Assert.AreEqual(true, result); + Assert.AreEqual(null, Environment.GetEnvironmentVariable("ASPNETCORE_URLS")); + + Environment.SetEnvironmentVariable("PORT", _prePort); + Environment.SetEnvironmentVariable("ASPNETCORE_URLS", _preAspNetUrls); + + // remove afterwards + new StorageHostFullPathFilesystem().FileDelete(appSettingsPath); + } + + + [TestMethod] + public async Task SkipForAppSettingsJsonFile_ShouldFalse() + { + var result = await PortProgramHelper.SkipForAppSettingsJsonFile(string.Empty); + Assert.AreEqual(false, result); + } + + [TestMethod] + public void SetDefaultAspNetCoreUrls_ShouldSet() + { + Environment.SetEnvironmentVariable("PORT", ""); + Environment.SetEnvironmentVariable("ASPNETCORE_URLS", ""); + + PortProgramHelper.SetDefaultAspNetCoreUrls(Array.Empty()); + + // should set to default + Assert.AreEqual("http://localhost:4000;https://localhost:4001", + Environment.GetEnvironmentVariable("ASPNETCORE_URLS")); + + Environment.SetEnvironmentVariable("PORT", _prePort); + Environment.SetEnvironmentVariable("ASPNETCORE_URLS", _preAspNetUrls); + } + + [TestMethod] + public void SetDefaultAspNetCoreUrls_ShouldIgnore() + { + Environment.SetEnvironmentVariable("PORT", ""); + Environment.SetEnvironmentVariable("ASPNETCORE_URLS", "http://localhost:4000"); + + PortProgramHelper.SetDefaultAspNetCoreUrls(Array.Empty()); + + // should set port to 4000 + Assert.AreEqual("http://localhost:4000", + Environment.GetEnvironmentVariable("ASPNETCORE_URLS")); + + Environment.SetEnvironmentVariable("PORT", _prePort); + Environment.SetEnvironmentVariable("ASPNETCORE_URLS", _preAspNetUrls); + } +} diff --git a/starsky/starskytest/starskytest.csproj b/starsky/starskytest/starskytest.csproj index 8788179aed..2e55dfdfe0 100644 --- a/starsky/starskytest/starskytest.csproj +++ b/starsky/starskytest/starskytest.csproj @@ -59,7 +59,7 @@ - + From 9bb3320078fa04ae5979831bbdbe4651d766ce4d Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 21 Feb 2024 10:44:55 +0100 Subject: [PATCH 062/125] using --- starsky/starsky/Program.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/starsky/starsky/Program.cs b/starsky/starsky/Program.cs index 9714caa7db..bf1c2ff7c3 100644 --- a/starsky/starsky/Program.cs +++ b/starsky/starsky/Program.cs @@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; -using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Models; using starsky.project.web.Helpers; From 5e1363bf84782fd0484101a651e9a824cb3e5e6b Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 21 Feb 2024 17:00:58 +0100 Subject: [PATCH 063/125] add check && change menu item desktop app --- .../Service/OpenEditorDesktopService.cs | 9 ++- .../IOpenApplicationNativeService.cs | 23 ++++++- .../OpenApplicationNativeService.cs | 51 ++++++++++++++- .../FakeIOpenApplicationNativeService.cs | 10 ++- .../Service/OpenEditorDesktopServiceTest.cs | 25 +++++++- .../OpenApplicationNativeServiceTest.cs | 64 ++++++++++++++++++- starskydesktop/src/app/menu/app-menu.ts | 17 ++--- 7 files changed, 175 insertions(+), 24 deletions(-) diff --git a/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs b/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs index 9af8c1443c..9579cd2b1a 100644 --- a/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs +++ b/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs @@ -42,8 +42,13 @@ public OpenEditorDesktopService(AppSettings appSettings, return ( null, "UseLocalDesktop feature toggle is disabled", [] ); } - var subPathAndImageFormatList = - await _openEditorPreflight.PreflightAsync(subPaths, collections); + if ( !_openApplicationNativeService.DetectToUseOpenApplication() ) + { + return ( null, "OpenEditor is not supported on this configuration", [] ); + } + + var subPathAndImageFormatList = await _openEditorPreflight + .PreflightAsync(subPaths, collections); if ( subPathAndImageFormatList.Count == 0 ) { diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs b/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs index d8fdb0ff2e..76c6ea6831 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/Interfaces/IOpenApplicationNativeService.cs @@ -2,7 +2,26 @@ namespace starsky.foundation.native.OpenApplicationNative.Interfaces; public interface IOpenApplicationNativeService { - bool? OpenApplicationAtUrl(List<(string, string)> fullPathAndApplicationUrl); - bool? OpenApplicationAtUrl(List fullPaths, string applicationUrl); + /// + /// Check if the system is supported to open a file + /// Not all configurations are supported + /// + /// true is supported and false is not supported + bool DetectToUseOpenApplication(); + + /// + /// Open with Default Editor + /// Please check DetectToUseOpenApplication() before using this method + /// + /// List first item is fullFilePath, second is ApplicationUrl + /// open = true, null is unsupported + bool? OpenApplicationAtUrl(List<(string fullFilePath, string applicationUrl)> fullPathAndApplicationUrl); + + /// + /// Open with Default Editor + /// Please check DetectToUseOpenApplication() before using this method + /// + /// Paths on disk + /// open = true, null is unsupported bool? OpenDefault(List fullPaths); } diff --git a/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs b/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs index b379ab0ba7..0ce2cc0d12 100644 --- a/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs +++ b/starsky/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeService.cs @@ -1,3 +1,4 @@ +using System.Runtime.InteropServices; using starsky.foundation.injection; using starsky.foundation.native.Helpers; using starsky.foundation.native.OpenApplicationNative.Helpers; @@ -8,12 +9,56 @@ namespace starsky.foundation.native.OpenApplicationNative; [Service(typeof(IOpenApplicationNativeService), InjectionLifetime = InjectionLifetime.Scoped)] public class OpenApplicationNativeService : IOpenApplicationNativeService { + /// + /// Is Open File supported on this configuration + /// + /// true if supported, false if not supported + public bool DetectToUseOpenApplication() + { + return DetectToUseOpenApplicationInternal(RuntimeInformation.IsOSPlatform, + Environment.UserInteractive); + } + + /// + /// Use to overwrite the RuntimeInformation.IsOSPlatform + /// + internal delegate bool IsOsPlatformDelegate(OSPlatform osPlatform); + + /// + /// Is Open File supported on this configuration + /// + /// RuntimeInformation.IsOSPlatform + /// Environment.UserInteractive + /// true if supported, false if not supported + internal static bool DetectToUseOpenApplicationInternal( + IsOsPlatformDelegate runtimeInformationIsOsPlatform, + bool environmentUserInteractive) + { + // Linux is not supported yet + if ( runtimeInformationIsOsPlatform(OSPlatform.Linux) || + runtimeInformationIsOsPlatform(OSPlatform.FreeBSD) ) + { + return false; + } + + // When running in Windows as Service it does not open the application + // On Mac OS it does open the application + if ( !environmentUserInteractive && runtimeInformationIsOsPlatform(OSPlatform.Windows) ) + { + return false; + } + + return true; + } + + /// /// Open file with specified application /// /// List first item is fullFilePath, second is ApplicationUrl /// true is operation succeed, false failed | null is platform unsupported - public bool? OpenApplicationAtUrl(List<(string, string)> fullPathAndApplicationUrl) + public bool? OpenApplicationAtUrl( + List<(string fullFilePath, string applicationUrl)> fullPathAndApplicationUrl) { if ( fullPathAndApplicationUrl.Count == 0 ) { @@ -42,7 +87,7 @@ public class OpenApplicationNativeService : IOpenApplicationNativeService /// full path style /// applicationUrl /// true is operation succeed, false failed | null is platform unsupported - public bool? OpenApplicationAtUrl(List fullPaths, string applicationUrl) + internal static bool? OpenApplicationAtUrl(List fullPaths, string applicationUrl) { var currentPlatform = OperatingSystemHelper.GetPlatform(); var macOsOpenResult = MacOsOpenUrl.OpenApplicationAtUrl(fullPaths, @@ -55,7 +100,7 @@ public class OpenApplicationNativeService : IOpenApplicationNativeService } internal static List<(List, string)> SortToOpenFilesByApplicationPath( - List<(string, string)> fullPathAndApplicationUrl) + List<(string fullFilePath, string applicationUrl)> fullPathAndApplicationUrl) { // Group applications by their names var groupedApplications = fullPathAndApplicationUrl.GroupBy(x => x.Item2).ToList(); diff --git a/starsky/starskytest/FakeMocks/FakeIOpenApplicationNativeService.cs b/starsky/starskytest/FakeMocks/FakeIOpenApplicationNativeService.cs index df44559387..92dc55a5dc 100644 --- a/starsky/starskytest/FakeMocks/FakeIOpenApplicationNativeService.cs +++ b/starsky/starskytest/FakeMocks/FakeIOpenApplicationNativeService.cs @@ -8,11 +8,14 @@ public class FakeIOpenApplicationNativeService : IOpenApplicationNativeService { private readonly List _fullFilePaths; private readonly string _applicationUrl; + private readonly bool _isSupported; - public FakeIOpenApplicationNativeService(List fullPaths, string applicationUrl) + public FakeIOpenApplicationNativeService(List fullPaths, string applicationUrl, + bool isSupported = true) { _fullFilePaths = fullPaths; _applicationUrl = applicationUrl; + _isSupported = isSupported; } public string FindPath(List fullPaths) @@ -30,6 +33,11 @@ public string FindPath(List fullPaths) return fullFilePath; } + public bool DetectToUseOpenApplication() + { + return _isSupported; + } + public bool? OpenApplicationAtUrl(List<(string, string)> fullPathAndApplicationUrl) { var filesByApplicationPath = diff --git a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs index 61a24064bd..1c44104412 100644 --- a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs +++ b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs @@ -59,8 +59,8 @@ public async Task OpenAsync_stringInput_HappyFlow() Assert.AreEqual("/test.jpg", list[0].SubPath); Assert.AreEqual("test", list[0].AppPath); } - - + + [TestMethod] public async Task OpenAsync_ListInput_HappyFlow() { @@ -149,4 +149,25 @@ public async Task OpenAsync_ListInput_UseLocalDesktop_Null() Assert.AreEqual("UseLocalDesktop feature toggle is disabled", status); Assert.AreEqual(0, list.Count); } + + [TestMethod] + public async Task OpenAsync_ListInput_UnSupportedPlatform() + { + var fakeService = + new FakeIOpenApplicationNativeService(new List(), string.Empty, false); + + var appSettings = new AppSettings { UseLocalDesktop = true }; + + var preflight = new FakeIOpenEditorPreflight(new List()); + + var service = + new OpenEditorDesktopService(appSettings, fakeService, preflight); + + var (success, status, list) = + ( await service.OpenAsync(new List { "/test.jpg" }, true) ); + + Assert.IsNull(success); + Assert.AreEqual("OpenEditor is not supported on this configuration", status); + Assert.AreEqual(0, list.Count); + } } diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs index 4e9a948723..26ecc336fa 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/OpenApplicationNativeServiceTest.cs @@ -10,6 +10,7 @@ using starsky.foundation.native.OpenApplicationNative.Helpers; using starsky.foundation.platform.Models; using starskytest.FakeCreateAn.CreateFakeStarskyExe; +using starskytest.starsky.foundation.native.Helpers; namespace starskytest.starsky.foundation.native.OpenApplicationNative; @@ -105,7 +106,7 @@ public async Task Service_OpenDefault_HappyFlow__WindowsOnly() [TestMethod] public void OpenApplicationAtUrl_ZeroItems_SoFalse() { - var result = new OpenApplicationNativeService().OpenApplicationAtUrl([], "app"); + var result = OpenApplicationNativeService.OpenApplicationAtUrl([], "app"); // Linux and FreeBSD are not supported if ( OperatingSystemHelper.GetPlatform() == OSPlatform.Linux || @@ -234,4 +235,65 @@ public void SortToOpenFilesByApplicationPath_MultipleApplications_ReturnsMultipl Assert.IsTrue(result.Exists(x => x.Item2 == "app2")); Assert.IsTrue(result.Exists(x => x.Item2 == "app3")); } + + [TestMethod] + public void DetectToUseOpenApplication_Default() + { + var result = new OpenApplicationNativeService().DetectToUseOpenApplication(); + + // Depending on the environment + if ( !Environment.UserInteractive && new AppSettings().IsWindows ) + { + Assert.IsFalse(result); + return; + } + + // Linux and FreeBSD are not supported + if ( OperatingSystemHelper.GetPlatform() == OSPlatform.Linux || + OperatingSystemHelper.GetPlatform() == OSPlatform.FreeBSD ) + { + Assert.IsFalse(result); + return; + } + + Assert.IsTrue(result); + } + + [TestMethod] + public void DetectToUseOpenApplicationInternal_Windows_AsWindowsService_InteractiveFalse() + { + var result = + OpenApplicationNativeService.DetectToUseOpenApplicationInternal( + FakeOsOverwrite.IsWindows, + false); + Assert.IsFalse(result); + } + + [TestMethod] + public void DetectToUseOpenApplicationInternal_MacOS_AsLaunchService_InteractiveTrue() + { + var result = + OpenApplicationNativeService.DetectToUseOpenApplicationInternal(FakeOsOverwrite.IsMacOs, + false); + Assert.IsTrue(result); + } + + [TestMethod] + public void DetectToUseOpenApplicationInternal_MacOS_Interactive_InteractiveTrue() + { + var result = + OpenApplicationNativeService.DetectToUseOpenApplicationInternal(FakeOsOverwrite.IsMacOs, + true); + Assert.IsTrue(result); + } + + + [TestMethod] + public void DetectToUseOpenApplicationInternal_Linux_Interactive_Interactive_False() + { + var result = + OpenApplicationNativeService.DetectToUseOpenApplicationInternal(FakeOsOverwrite.IsLinux, + true); + Assert.IsFalse(result); + } } diff --git a/starskydesktop/src/app/menu/app-menu.ts b/starskydesktop/src/app/menu/app-menu.ts index 0b20d221a7..eca298102c 100644 --- a/starskydesktop/src/app/menu/app-menu.ts +++ b/starskydesktop/src/app/menu/app-menu.ts @@ -151,9 +151,7 @@ function AppMenu() { label: "Open in browser", // eslint-disable-next-line @typescript-eslint/no-misused-promises click: async () => { - await shell.openExternal( - BrowserWindow.getFocusedWindow().webContents.getURL() - ); + await shell.openExternal(BrowserWindow.getFocusedWindow().webContents.getURL()); }, }, ], @@ -189,12 +187,7 @@ function AppMenu() { }, { role: "zoom" }, ...(isMac - ? [ - { type: "separator" }, - { role: "front" }, - { type: "separator" }, - { role: "window" }, - ] + ? [{ type: "separator" }, { role: "front" }, { type: "separator" }, { role: "window" }] : [{ role: "close" }]), ], }, @@ -205,7 +198,7 @@ function AppMenu() { label: "Documentation website", // eslint-disable-next-line @typescript-eslint/no-misused-promises click: async () => { - await shell.openExternal("https://docs.qdraw.nl/download"); + await shell.openExternal("https://docs.qdraw.nl/docs/getting-started/first-steps"); }, }, { @@ -213,9 +206,7 @@ function AppMenu() { // Referenced from HealthCheckForUpdates // eslint-disable-next-line @typescript-eslint/no-misused-promises click: async () => { - await shell.openExternal( - "https://github.com/qdraw/starsky/releases/latest" - ); + await shell.openExternal("https://github.com/qdraw/starsky/releases/latest"); }, }, ], From a60e5c6f396e53175efc13b120f408d412204248 Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 21 Feb 2024 17:04:15 +0100 Subject: [PATCH 064/125] fixes --- .../Interfaces/IOpenEditorDesktopService.cs | 12 ++++++ .../Service/OpenEditorDesktopService.cs | 6 +++ .../Interfaces/IMoveToTrashService.cs | 7 --- .../Services/MoveToTrashService.cs | 31 ++++++------- .../ViewModels/EnvFeaturesViewModel.cs | 5 +++ .../AppSettingsFeaturesController.cs | 5 +++ .../starsky/Controllers/TrashController.cs | 19 ++------ .../starsky/clientapp/src/shared/url-query.ts | 2 +- .../AppSettingsFeaturesControllerTest.cs | 8 ++-- .../Controllers/TrashControllerTest.cs | 30 ++++--------- .../FakeIOpenEditorDesktopService.cs | 43 +++++++++++++++++++ .../Service/OpenEditorDesktopServiceTest.cs | 11 +++-- .../Services/MoveToTrashServiceTest.cs | 14 ------ 13 files changed, 105 insertions(+), 88 deletions(-) create mode 100644 starsky/starskytest/FakeMocks/FakeIOpenEditorDesktopService.cs diff --git a/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs b/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs index 94590660be..d0a85763e5 100644 --- a/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs +++ b/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs @@ -4,6 +4,18 @@ namespace starsky.feature.desktop.Interfaces; public interface IOpenEditorDesktopService { + /// + /// Is supported and enabled in the feature toggle + /// + /// Should you use it? + bool IsEnabled(); + + /// + /// Open a file in the default editor or specific editor which is set in the app settings + /// + /// dot comma split list with subPaths + /// should pick raw/jpeg file even its not specified + /// files done and list of results Task<(bool?, string, List)> OpenAsync(string f, bool collections); } diff --git a/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs b/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs index 9579cd2b1a..7846050eb6 100644 --- a/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs +++ b/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs @@ -27,6 +27,12 @@ public OpenEditorDesktopService(AppSettings appSettings, _openEditorPreflight = openEditorPreflight; } + public bool IsEnabled() + { + return _appSettings.UseLocalDesktop == true && + _openApplicationNativeService.DetectToUseOpenApplication(); + } + public async Task<(bool?, string, List)> OpenAsync(string f, bool collections) { diff --git a/starsky/starsky.feature.trash/Interfaces/IMoveToTrashService.cs b/starsky/starsky.feature.trash/Interfaces/IMoveToTrashService.cs index 469dcaf294..d4b04a32b0 100644 --- a/starsky/starsky.feature.trash/Interfaces/IMoveToTrashService.cs +++ b/starsky/starsky.feature.trash/Interfaces/IMoveToTrashService.cs @@ -14,13 +14,6 @@ public interface IMoveToTrashService Task> MoveToTrashAsync(List inputFilePaths, bool collections); - /// - /// Is it supported to use the system trash - /// But it does NOT check if the feature toggle is enabled - /// - /// true if supported - bool DetectToUseSystemTrash(); - /// /// Is supported and enabled in the feature toggle /// diff --git a/starsky/starsky.feature.trash/Services/MoveToTrashService.cs b/starsky/starsky.feature.trash/Services/MoveToTrashService.cs index 1f09bfc099..025ce4c603 100644 --- a/starsky/starsky.feature.trash/Services/MoveToTrashService.cs +++ b/starsky/starsky.feature.trash/Services/MoveToTrashService.cs @@ -10,6 +10,7 @@ using starsky.foundation.worker.Interfaces; [assembly: InternalsVisibleTo("starskytest")] + namespace starsky.feature.trash.Services; [Service(typeof(IMoveToTrashService), InjectionLifetime = InjectionLifetime.Scoped)] @@ -46,17 +47,7 @@ ITrashConnectionService connectionService public bool IsEnabled() { return _appSettings.UseSystemTrash == true && - _systemTrashService.DetectToUseSystemTrash(); - } - - /// - /// Is it supported to use the system trash - /// But it does NOT check if the feature toggle is enabled - /// - /// true if supported - public bool DetectToUseSystemTrash() - { - return _systemTrashService.DetectToUseSystemTrash(); + _systemTrashService.DetectToUseSystemTrash(); } /// @@ -74,11 +65,13 @@ public async Task> MoveToTrashAsync( await _metaPreflight.PreflightAsync(inputModel, inputFilePaths, false, collections, 0); - (fileIndexResultsList, changedFileIndexItemName) = await AppendChildItemsToTrashList(fileIndexResultsList, changedFileIndexItemName); + ( fileIndexResultsList, changedFileIndexItemName ) = + await AppendChildItemsToTrashList(fileIndexResultsList, changedFileIndexItemName); var moveToTrashList = fileIndexResultsList.Where(p => - p.Status is FileIndexItem.ExifStatus.Ok or FileIndexItem.ExifStatus.Deleted).ToList(); + p.Status is FileIndexItem.ExifStatus.Ok or FileIndexItem.ExifStatus.Deleted) + .ToList(); var isSystemTrashEnabled = IsEnabled(); @@ -92,9 +85,8 @@ await _queue.QueueBackgroundWorkItemAsync(async _ => return; } - await MetaTrashInQueue(changedFileIndexItemName!, + await MetaTrashInQueue(changedFileIndexItemName, fileIndexResultsList, inputModel, collections); - }, "trash"); return TrashConnectionService.StatusUpdate(moveToTrashList, isSystemTrashEnabled); @@ -112,8 +104,9 @@ await _metaUpdateService.UpdateAsync(changedFileIndexItemName, /// /// /// - internal async Task<(List, Dictionary>?)> AppendChildItemsToTrashList(List moveToTrash, - Dictionary> changedFileIndexItemName) + internal async Task<(List, Dictionary>)> + AppendChildItemsToTrashList(List moveToTrash, + Dictionary> changedFileIndexItemName) { var parentSubPaths = moveToTrash .Where(p => !string.IsNullOrEmpty(p.FilePath) && p.IsDirectory == true) @@ -122,7 +115,7 @@ await _metaUpdateService.UpdateAsync(changedFileIndexItemName, if ( parentSubPaths.Count == 0 ) { - return (moveToTrash, changedFileIndexItemName); + return ( moveToTrash, changedFileIndexItemName ); } var childItems = ( await _query.GetAllObjectsAsync(parentSubPaths) ) @@ -139,7 +132,7 @@ await _metaUpdateService.UpdateAsync(changedFileIndexItemName, changedFileIndexItemName.TryAdd(childItem.FilePath!, new List { "tags" }); } - return (moveToTrash, changedFileIndexItemName); + return ( moveToTrash, changedFileIndexItemName ); } private async Task SystemTrashInQueue(List moveToTrash) diff --git a/starsky/starsky.project.web/ViewModels/EnvFeaturesViewModel.cs b/starsky/starsky.project.web/ViewModels/EnvFeaturesViewModel.cs index 60a8942cf1..34f731c7fd 100644 --- a/starsky/starsky.project.web/ViewModels/EnvFeaturesViewModel.cs +++ b/starsky/starsky.project.web/ViewModels/EnvFeaturesViewModel.cs @@ -11,4 +11,9 @@ public class EnvFeaturesViewModel /// Enable or disable some features on the frontend /// public bool UseLocalDesktop { get; set; } + + /// + /// Is supported and enabled in the feature toggle + /// + public bool OpenEditorEnabled { get; set; } } diff --git a/starsky/starsky/Controllers/AppSettingsFeaturesController.cs b/starsky/starsky/Controllers/AppSettingsFeaturesController.cs index 1ca956296d..a41dcee8c6 100644 --- a/starsky/starsky/Controllers/AppSettingsFeaturesController.cs +++ b/starsky/starsky/Controllers/AppSettingsFeaturesController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using starsky.feature.desktop.Interfaces; using starsky.feature.trash.Interfaces; using starsky.foundation.platform.Models; using starsky.project.web.ViewModels; @@ -10,11 +11,14 @@ public class AppSettingsFeaturesController : Controller { private readonly IMoveToTrashService _moveToTrashService; private readonly AppSettings _appSettings; + private readonly IOpenEditorDesktopService _openEditorDesktopService; public AppSettingsFeaturesController(IMoveToTrashService moveToTrashService, + IOpenEditorDesktopService openEditorDesktopService, AppSettings appSettings) { _moveToTrashService = moveToTrashService; + _openEditorDesktopService = openEditorDesktopService; _appSettings = appSettings; } @@ -38,6 +42,7 @@ public IActionResult FeaturesView() { SystemTrashEnabled = _moveToTrashService.IsEnabled(), UseLocalDesktop = _appSettings.UseLocalDesktop == true, + OpenEditorEnabled = _openEditorDesktopService.IsEnabled() }; return Json(shortAppSettings); diff --git a/starsky/starsky/Controllers/TrashController.cs b/starsky/starsky/Controllers/TrashController.cs index 555674fc3d..d4d7bc2bbc 100644 --- a/starsky/starsky/Controllers/TrashController.cs +++ b/starsky/starsky/Controllers/TrashController.cs @@ -20,21 +20,7 @@ public TrashController(IMoveToTrashService moveToTrashService) } /// - /// Is the system trash supported - /// - /// bool with json (IActionResult Result) - /// the item including the updated content - /// User unauthorized - [ProducesResponseType(typeof(bool), 200)] - [HttpGet("/api/trash/detect-to-use-system-trash")] - [Produces("application/json")] - public IActionResult DetectToUseSystemTrash() - { - return Json(_moveToTrashService.DetectToUseSystemTrash()); - } - - /// - /// (beta) Move a file to the trash + /// Move a file to the trash /// /// subPath filepath to file, split by dot comma (;) /// stack collections @@ -56,7 +42,8 @@ public async Task TrashMoveAsync(string f, bool collections = fal return BadRequest("No input files"); } - var fileIndexResultsList = await _moveToTrashService.MoveToTrashAsync(inputFilePaths.ToList(), collections); + var fileIndexResultsList = + await _moveToTrashService.MoveToTrashAsync(inputFilePaths.ToList(), collections); return Json(fileIndexResultsList); } diff --git a/starsky/starsky/clientapp/src/shared/url-query.ts b/starsky/starsky/clientapp/src/shared/url-query.ts index d227f7dfbc..d6a84d0e2a 100644 --- a/starsky/starsky/clientapp/src/shared/url-query.ts +++ b/starsky/starsky/clientapp/src/shared/url-query.ts @@ -325,7 +325,7 @@ export class UrlQuery { }; public UrlApiFeaturesAppSettings = (): string => { - return this.prefix + "/api/env/features"; + return this.prefix + "/api/env/features?v=0.6.0-beta.2"; }; /** diff --git a/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs b/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs index e305c2a9e3..1bdaf0c382 100644 --- a/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs +++ b/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs @@ -18,7 +18,7 @@ public void FeaturesViewTest() // Arrange var fakeIMoveToTrashService = new FakeIMoveToTrashService(new List()); var appSettingsFeaturesController = new AppSettingsFeaturesController( - fakeIMoveToTrashService, new AppSettings()); + fakeIMoveToTrashService, new FakeIOpenEditorDesktopService(), new AppSettings()); // Act var result = appSettingsFeaturesController.FeaturesView() as JsonResult; @@ -35,7 +35,8 @@ public void FeaturesViewTest_Disabled() // Arrange var fakeIMoveToTrashService = new FakeIMoveToTrashService(new List(), false); var appSettingsFeaturesController = new AppSettingsFeaturesController( - fakeIMoveToTrashService, new AppSettings { UseLocalDesktop = false }); + fakeIMoveToTrashService, new FakeIOpenEditorDesktopService(), + new AppSettings { UseLocalDesktop = false }); // Act var result = appSettingsFeaturesController.FeaturesView() as JsonResult; @@ -53,7 +54,8 @@ public void FeaturesViewTest_Enabled() // Arrange var fakeIMoveToTrashService = new FakeIMoveToTrashService(new List()); var appSettingsFeaturesController = new AppSettingsFeaturesController( - fakeIMoveToTrashService, new AppSettings { UseLocalDesktop = true }); + fakeIMoveToTrashService, new FakeIOpenEditorDesktopService(), + new AppSettings { UseLocalDesktop = true }); // Act var result = appSettingsFeaturesController.FeaturesView() as JsonResult; diff --git a/starsky/starskytest/Controllers/TrashControllerTest.cs b/starsky/starskytest/Controllers/TrashControllerTest.cs index f19ea3f9cf..b0f18a4790 100644 --- a/starsky/starskytest/Controllers/TrashControllerTest.cs +++ b/starsky/starskytest/Controllers/TrashControllerTest.cs @@ -17,9 +17,9 @@ public async Task TrashControllerTest_BadInput() var controller = new TrashController( new FakeIMoveToTrashService(new List())); var result = await controller.TrashMoveAsync(null!, true) as BadRequestObjectResult; - Assert.AreEqual(400,result?.StatusCode); + Assert.AreEqual(400, result?.StatusCode); } - + [TestMethod] public async Task TrashControllerTest_NotFound() { @@ -27,35 +27,21 @@ public async Task TrashControllerTest_NotFound() new FakeIMoveToTrashService(new List())); var result = await controller.TrashMoveAsync("/test.jpg", true) as JsonResult; var resultValue = result?.Value as List; - + Assert.AreEqual(1, resultValue?.Count); } - + [TestMethod] public async Task TrashControllerTest_Ok() { var controller = new TrashController( - new FakeIMoveToTrashService(new List{new FileIndexItem("/test.jpg") + new FakeIMoveToTrashService(new List { - Status = FileIndexItem.ExifStatus.Ok - }})); + new FileIndexItem("/test.jpg") { Status = FileIndexItem.ExifStatus.Ok } + })); var result = await controller.TrashMoveAsync("/test.jpg", true) as JsonResult; var resultValue = result?.Value as List; - + Assert.AreEqual(1, resultValue?.Count); } - - [TestMethod] - public void DetectToUseSystemTrash_Ok() - { - var controller = new TrashController( - new FakeIMoveToTrashService(new List())); - - var result = controller.DetectToUseSystemTrash() as JsonResult; - - var tryParseResult = bool.TryParse(result?.Value?.ToString(), out var resultValue); - - Assert.AreEqual(true, tryParseResult); - Assert.AreEqual(true, resultValue); - } } diff --git a/starsky/starskytest/FakeMocks/FakeIOpenEditorDesktopService.cs b/starsky/starskytest/FakeMocks/FakeIOpenEditorDesktopService.cs new file mode 100644 index 0000000000..850dd7e153 --- /dev/null +++ b/starsky/starskytest/FakeMocks/FakeIOpenEditorDesktopService.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using starsky.feature.desktop.Interfaces; +using starsky.feature.desktop.Models; +using starsky.foundation.database.Models; +using starsky.foundation.platform.Helpers; + +namespace starskytest.FakeMocks; + +public class FakeIOpenEditorDesktopService : IOpenEditorDesktopService +{ + private readonly bool _isEnabled; + + public FakeIOpenEditorDesktopService(bool isEnabled = true) + { + _isEnabled = isEnabled; + } + + public bool IsEnabled() + { + return _isEnabled; + } + + public async Task<(bool?, string, List)> OpenAsync(string f, + bool collections) + { + await Task.Yield(); + + var list = new List + { + new PathImageFormatExistsAppPathModel + { + AppPath = "test", + Status = FileIndexItem.ExifStatus.Ok, + ImageFormat = ExtensionRolesHelper.ImageFormat.jpg, + SubPath = "/test.jpg", + FullFilePath = "/test.jpg" + } + }; + + return ( _isEnabled, "Opened", list ); + } +} diff --git a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs index 1c44104412..f9ce24cb34 100644 --- a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs +++ b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs @@ -16,8 +16,8 @@ public class OpenEditorDesktopServiceTest [TestMethod] public async Task OpenAsync_stringInput_HappyFlow() { - var fakeService = - new FakeIOpenApplicationNativeService(new List { "/test.jpg" }, "test"); + var fakeService = new FakeIOpenApplicationNativeService( + new List { "/test.jpg" }, "test"); var appSettings = new AppSettings { @@ -153,15 +153,14 @@ public async Task OpenAsync_ListInput_UseLocalDesktop_Null() [TestMethod] public async Task OpenAsync_ListInput_UnSupportedPlatform() { - var fakeService = - new FakeIOpenApplicationNativeService(new List(), string.Empty, false); + var fakeService = new FakeIOpenApplicationNativeService(new List(), + string.Empty, false); var appSettings = new AppSettings { UseLocalDesktop = true }; var preflight = new FakeIOpenEditorPreflight(new List()); - var service = - new OpenEditorDesktopService(appSettings, fakeService, preflight); + var service = new OpenEditorDesktopService(appSettings, fakeService, preflight); var (success, status, list) = ( await service.OpenAsync(new List { "/test.jpg" }, true) ); diff --git a/starsky/starskytest/starsky.feature.trash/Services/MoveToTrashServiceTest.cs b/starsky/starskytest/starsky.feature.trash/Services/MoveToTrashServiceTest.cs index 19c7fac3bd..b0373989e7 100644 --- a/starsky/starskytest/starsky.feature.trash/Services/MoveToTrashServiceTest.cs +++ b/starsky/starskytest/starsky.feature.trash/Services/MoveToTrashServiceTest.cs @@ -313,20 +313,6 @@ public async Task InMetaTrash_WithDbContext_Directory() Assert.AreEqual(TrashKeyword.TrashKeywordString, result[1].Tags); } - [TestMethod] - public void DetectToUseSystemTrash_False() - { - var trashService = new FakeITrashService() { IsSupported = false }; - var moveToTrashService = new MoveToTrashService(new AppSettings(), new FakeIQuery(), - new FakeMetaPreflight(), new FakeIUpdateBackgroundTaskQueue(), - trashService, new FakeIMetaUpdateService(), - new FakeITrashConnectionService()); - - var result = moveToTrashService.DetectToUseSystemTrash(); - - Assert.AreEqual(false, result); - } - [TestMethod] public async Task AppendChildItemsToTrashList_NoAny() { From 6dd617d253b749463c54516e18ef50a8372c03b5 Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 21 Feb 2024 17:06:25 +0100 Subject: [PATCH 065/125] Add tests --- starsky-tools/mock/api/env/features.json | 2 +- .../Interfaces/IOpenEditorDesktopService.cs | 8 +++ .../Service/OpenEditorDesktopService.cs | 37 +++++++++++ .../Models/AppSettings.cs | 10 ++- .../Controllers/DesktopEditorController.cs | 18 ++++++ .../AppSettingsFeaturesControllerTest.cs | 4 +- .../DesktopEditorControllerTest.cs | 22 +++++++ .../FakeIOpenEditorDesktopService.cs | 12 +++- .../Service/OpenEditorDesktopServiceTest.cs | 62 +++++++++++++++++++ .../Helpers/MimeHelperTest.cs | 7 +++ 10 files changed, 178 insertions(+), 4 deletions(-) diff --git a/starsky-tools/mock/api/env/features.json b/starsky-tools/mock/api/env/features.json index 5246a3e88b..4f38575c57 100644 --- a/starsky-tools/mock/api/env/features.json +++ b/starsky-tools/mock/api/env/features.json @@ -1 +1 @@ -{"systemTrashEnabled":false,"useLocalDesktop":false} \ No newline at end of file +{"systemTrashEnabled":false,"useLocalDesktop":false, "openEditorEnabled": false} \ No newline at end of file diff --git a/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs b/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs index d0a85763e5..ccc8312200 100644 --- a/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs +++ b/starsky/starsky.feature.desktop/Interfaces/IOpenEditorDesktopService.cs @@ -4,6 +4,14 @@ namespace starsky.feature.desktop.Interfaces; public interface IOpenEditorDesktopService { + /// + /// Check if the file is less then the amount of files that are allowed to open + /// If there are more files to open it will return false and the front-end will ask for confirmation + /// + /// dot comma list of paths + /// true is no confirmation and false ask are you sure + bool OpenAmountConfirmationChecker(string f); + /// /// Is supported and enabled in the feature toggle /// diff --git a/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs b/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs index 7846050eb6..25e8aae808 100644 --- a/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs +++ b/starsky/starsky.feature.desktop/Service/OpenEditorDesktopService.cs @@ -27,6 +27,43 @@ public OpenEditorDesktopService(AppSettings appSettings, _openEditorPreflight = openEditorPreflight; } + /// + /// Get value from App Settings without getting a negative value + /// + /// setting + private int GetDesktopEditorAmountBeforeConfirmation() + { + var desktopEditorAmountBeforeConfirmation = + _appSettings.DesktopEditorAmountBeforeConfirmation ?? + DesktopEditorAmountBeforeConfirmationDefault; + if ( _appSettings.DesktopEditorAmountBeforeConfirmation <= 1 ) + { + desktopEditorAmountBeforeConfirmation = DesktopEditorAmountBeforeConfirmationDefault; + } + + return desktopEditorAmountBeforeConfirmation; + } + + /// + /// Default Setting for Desktop Editor Amount Before Confirmation + /// + private const int DesktopEditorAmountBeforeConfirmationDefault = 5; + + /// + /// Check for Desktop Editor Amount Before Confirmation + /// + /// dot comma seperated values + /// true + public bool OpenAmountConfirmationChecker(string f) + { + var inputFilePaths = PathHelper.SplitInputFilePaths(f); + return GetDesktopEditorAmountBeforeConfirmation() >= inputFilePaths.Length; + } + + /// + /// Is feature toggle enabled and supported + /// + /// true is feature toggle enabled and supported public bool IsEnabled() { return _appSettings.UseLocalDesktop == true && diff --git a/starsky/starsky.foundation.platform/Models/AppSettings.cs b/starsky/starsky.foundation.platform/Models/AppSettings.cs index 69fcb6b63a..0a246d5f8f 100644 --- a/starsky/starsky.foundation.platform/Models/AppSettings.cs +++ b/starsky/starsky.foundation.platform/Models/AppSettings.cs @@ -731,6 +731,9 @@ public Dictionary? AccountRolesByEmailRegisterOverwrite "/lost+found", "/.stfolder", "/.git" }; + /// + /// Auto Sync on Startup + /// public bool? SyncOnStartup { get; set; } = true; /// @@ -747,6 +750,7 @@ public Dictionary? AccountRolesByEmailRegisterOverwrite /// But it seems a lot of cameras don't do this /// We assume that the standard is followed, and for Camera brands that don't follow the specs use this setting. /// + [PackageTelemetry] public List? VideoUseLocalTime { get; set; } = new List { new CameraMakeModel("Sony", "A58") @@ -757,7 +761,6 @@ public Dictionary? AccountRolesByEmailRegisterOverwrite /// private bool? EnablePackageTelemetryPrivate { get; set; } - /// /// Disable logout buttons in UI /// And hides server specific features that are strange on a local desktop @@ -779,6 +782,11 @@ public Dictionary? AccountRolesByEmailRegisterOverwrite public CollectionsOpenType.RawJpegMode DesktopCollectionsOpen { get; set; } = CollectionsOpenType.RawJpegMode.Default; + /// + /// Number of files to open before confirmation + /// + public int? DesktopEditorAmountBeforeConfirmation { get; set; } + /// /// Helps us improve the software /// Please keep this enabled diff --git a/starsky/starsky/Controllers/DesktopEditorController.cs b/starsky/starsky/Controllers/DesktopEditorController.cs index 846eb2e262..6b57335577 100644 --- a/starsky/starsky/Controllers/DesktopEditorController.cs +++ b/starsky/starsky/Controllers/DesktopEditorController.cs @@ -50,4 +50,22 @@ public async Task OpenAsync( return Json(list); } + + + /// + /// Check the amount of files to open before + /// + /// single or multiple subPaths + /// + /// bool, true is no confirmation, false is ask confirmation + /// User unauthorized + [HttpGet("/api/desktop-editor/amount-confirmation")] + [Produces("application/json")] + [ProducesResponseType(typeof(bool), 200)] + [ProducesResponseType(401)] + public IActionResult OpenAmountConfirmationChecker(string f) + { + var result = _openEditorDesktopService.OpenAmountConfirmationChecker(f); + return Json(result); + } } diff --git a/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs b/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs index 1bdaf0c382..bf286b0d51 100644 --- a/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs +++ b/starsky/starskytest/Controllers/AppSettingsFeaturesControllerTest.cs @@ -35,7 +35,7 @@ public void FeaturesViewTest_Disabled() // Arrange var fakeIMoveToTrashService = new FakeIMoveToTrashService(new List(), false); var appSettingsFeaturesController = new AppSettingsFeaturesController( - fakeIMoveToTrashService, new FakeIOpenEditorDesktopService(), + fakeIMoveToTrashService, new FakeIOpenEditorDesktopService(false), new AppSettings { UseLocalDesktop = false }); // Act @@ -46,6 +46,7 @@ public void FeaturesViewTest_Disabled() // Assert Assert.IsFalse(json.UseLocalDesktop); Assert.IsFalse(json.SystemTrashEnabled); + Assert.IsFalse(json.OpenEditorEnabled); } [TestMethod] @@ -65,5 +66,6 @@ public void FeaturesViewTest_Enabled() // Assert Assert.IsTrue(json.UseLocalDesktop); Assert.IsTrue(json.SystemTrashEnabled); + Assert.IsTrue(json.OpenEditorEnabled); } } diff --git a/starsky/starskytest/Controllers/DesktopEditorControllerTest.cs b/starsky/starskytest/Controllers/DesktopEditorControllerTest.cs index c8c90938ca..00bc80e008 100644 --- a/starsky/starskytest/Controllers/DesktopEditorControllerTest.cs +++ b/starsky/starskytest/Controllers/DesktopEditorControllerTest.cs @@ -16,6 +16,28 @@ namespace starskytest.Controllers; [TestClass] public class DesktopEditorControllerTest { + [TestMethod] + public void OpenAmountConfirmationChecker_FeatureToggleEnabled() + { + var controller = new DesktopEditorController( + new OpenEditorDesktopService(new AppSettings(), + new FakeIOpenApplicationNativeService(new List(), "test"), + new FakeIOpenEditorPreflight(new List()))); + + controller.ControllerContext = new ControllerContext + { + HttpContext = new DefaultHttpContext() + }; + + var result = controller.OpenAmountConfirmationChecker("/test.jpg;/test2.jpg"); + + var castedResult = ( JsonResult )result; + var boolValue = ( bool? )castedResult.Value; + // mock is always true + + Assert.IsTrue(boolValue); + } + [TestMethod] public async Task OpenAsync_FeatureToggleDisabled() { diff --git a/starsky/starskytest/FakeMocks/FakeIOpenEditorDesktopService.cs b/starsky/starskytest/FakeMocks/FakeIOpenEditorDesktopService.cs index 850dd7e153..a7d00f0090 100644 --- a/starsky/starskytest/FakeMocks/FakeIOpenEditorDesktopService.cs +++ b/starsky/starskytest/FakeMocks/FakeIOpenEditorDesktopService.cs @@ -11,11 +11,21 @@ public class FakeIOpenEditorDesktopService : IOpenEditorDesktopService { private readonly bool _isEnabled; - public FakeIOpenEditorDesktopService(bool isEnabled = true) + public FakeIOpenEditorDesktopService() + { + _isEnabled = true; + } + + public FakeIOpenEditorDesktopService(bool isEnabled) { _isEnabled = isEnabled; } + public bool OpenAmountConfirmationChecker(string f) + { + return true; + } + public bool IsEnabled() { return _isEnabled; diff --git a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs index f9ce24cb34..c447258c5b 100644 --- a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs +++ b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs @@ -169,4 +169,66 @@ public async Task OpenAsync_ListInput_UnSupportedPlatform() Assert.AreEqual("OpenEditor is not supported on this configuration", status); Assert.AreEqual(0, list.Count); } + + [TestMethod] + public void OpenAmountConfirmationChecker_6Files() + { + var appSettings = new AppSettings { DesktopEditorAmountBeforeConfirmation = 5 }; + + var service = new OpenEditorDesktopService(appSettings, + new FakeIOpenApplicationNativeService(new List(), "test"), + new FakeIOpenEditorPreflight(new List())); + + var result = + service.OpenAmountConfirmationChecker( + "/test.jpg;/test2.jpg;/test3.jpg;/test4.jpg;/test5.jpg;/test6.jpg"); + Assert.IsFalse(result); + } + + [TestMethod] + public void OpenAmountConfirmationChecker_6Files_Null() + { + var appSettings = new AppSettings { DesktopEditorAmountBeforeConfirmation = null }; + + var service = new OpenEditorDesktopService(appSettings, + new FakeIOpenApplicationNativeService(new List(), "test"), + new FakeIOpenEditorPreflight(new List())); + + var result = + service.OpenAmountConfirmationChecker( + "/test.jpg;/test2.jpg;/test3.jpg;/test4.jpg;/test5.jpg;/test6.jpg"); + + // Assumes that the default value is 5 + Assert.IsFalse(result); + } + + [TestMethod] + public void OpenAmountConfirmationChecker_4Files() + { + var appSettings = new AppSettings { DesktopEditorAmountBeforeConfirmation = 4 }; + + var service = new OpenEditorDesktopService(appSettings, + new FakeIOpenApplicationNativeService(new List(), "test"), + new FakeIOpenEditorPreflight(new List())); + + var result = + service.OpenAmountConfirmationChecker("/test.jpg;/test2.jpg;/test3.jpg;/test4.jpg"); + Assert.IsTrue(result); + } + + [TestMethod] + public void OpenAmountConfirmationChecker_1File() + { + var appSettings = new AppSettings + { + DesktopEditorAmountBeforeConfirmation = -90 // invalid value + }; + + var service = new OpenEditorDesktopService(appSettings, + new FakeIOpenApplicationNativeService(new List(), "test"), + new FakeIOpenEditorPreflight(new List())); + + var result = service.OpenAmountConfirmationChecker("/test.jpg"); + Assert.IsTrue(result); + } } diff --git a/starsky/starskytest/starsky.project.web/Helpers/MimeHelperTest.cs b/starsky/starskytest/starsky.project.web/Helpers/MimeHelperTest.cs index 64ea21e9ed..90e6ab03ba 100644 --- a/starsky/starskytest/starsky.project.web/Helpers/MimeHelperTest.cs +++ b/starsky/starskytest/starsky.project.web/Helpers/MimeHelperTest.cs @@ -31,6 +31,13 @@ public void GetMimeTypeByExtensionTest_NoExtension() Assert.AreEqual("application/octet-stream", MimeHelper.GetMimeTypeByFileName(string.Empty)); } + + [TestMethod] + public void GetMimeTypeByExtensionTest_Null() + { + Assert.AreEqual("application/octet-stream", + MimeHelper.GetMimeTypeByFileName(null)); + } [TestMethod] public void GetMimeType_NoExtension() From ef2e573c3aa33d202b57f0b45925537789a29333 Mon Sep 17 00:00:00 2001 From: SwaggerUpdateBot Date: Wed, 21 Feb 2024 17:27:37 +0000 Subject: [PATCH 066/125] [Swagger] Auto commited swagger/openapi list --- documentation/static/openapi/openapi.json | 768 +++++++++++++++++++--- 1 file changed, 671 insertions(+), 97 deletions(-) diff --git a/documentation/static/openapi/openapi.json b/documentation/static/openapi/openapi.json index ca2178b38d..07e46a6cee 100644 --- a/documentation/static/openapi/openapi.json +++ b/documentation/static/openapi/openapi.json @@ -2674,6 +2674,348 @@ "parameters": [], "extensions": {} }, + "/api/desktop-editor/open": { + "operations": { + "Get": { + "tags": [ + { + "name": "DesktopEditor", + "extensions": {}, + "unresolvedReference": false + } + ], + "summary": "Open a file in the default editor or a specific editor on the desktop", + "parameters": [ + { + "unresolvedReference": false, + "name": "f", + "in": 0, + "description": "single or multiple subPaths", + "required": false, + "deprecated": false, + "allowEmptyValue": false, + "explode": false, + "allowReserved": false, + "schema": { + "type": "string", + "default": { + "primitiveType": 4, + "anyType": 0, + "value": "" + }, + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": false, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + }, + "examples": {}, + "content": {}, + "extensions": {} + }, + { + "unresolvedReference": false, + "name": "collections", + "in": 0, + "description": "to combine files with the same name before the extension", + "required": false, + "deprecated": false, + "allowEmptyValue": false, + "explode": false, + "allowReserved": false, + "schema": { + "type": "boolean", + "default": { + "primitiveType": 7, + "anyType": 0, + "value": true + }, + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": false, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + }, + "examples": {}, + "content": {}, + "extensions": {} + } + ], + "responses": { + "200": { + "description": "returns a list of items from the database", + "headers": {}, + "content": { + "application/json": { + "schema": { + "type": "array", + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "items": { + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": false, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false, + "reference": { + "type": 0, + "id": "PathImageFormatExistsAppPathModel", + "isExternal": false, + "isLocal": true, + "referenceV3": "#/components/schemas/PathImageFormatExistsAppPathModel", + "referenceV2": "#/definitions/PathImageFormatExistsAppPathModel" + } + }, + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": false, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + }, + "examples": {}, + "encoding": {}, + "extensions": {} + } + }, + "links": {}, + "extensions": {}, + "unresolvedReference": false + }, + "204": { + "description": "No Content", + "headers": {}, + "content": { + "application/json": { + "schema": { + "type": "array", + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "items": { + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": false, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false, + "reference": { + "type": 0, + "id": "PathImageFormatExistsAppPathModel", + "isExternal": false, + "isLocal": true, + "referenceV3": "#/components/schemas/PathImageFormatExistsAppPathModel", + "referenceV2": "#/definitions/PathImageFormatExistsAppPathModel" + } + }, + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": false, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + }, + "examples": {}, + "encoding": {}, + "extensions": {} + } + }, + "links": {}, + "extensions": {}, + "unresolvedReference": false + }, + "400": { + "description": "Bad Request", + "headers": {}, + "content": { + "application/json": { + "schema": { + "type": "string", + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": false, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + }, + "examples": {}, + "encoding": {}, + "extensions": {} + } + }, + "links": {}, + "extensions": {}, + "unresolvedReference": false + }, + "401": { + "description": "User unauthorized", + "headers": {}, + "content": {}, + "links": {}, + "extensions": {}, + "unresolvedReference": false + }, + "404": { + "description": "subPath not found in the database", + "headers": {}, + "content": {}, + "links": {}, + "extensions": {}, + "unresolvedReference": false + } + }, + "callbacks": {}, + "deprecated": false, + "security": [], + "servers": [], + "extensions": {} + } + }, + "servers": [], + "parameters": [], + "extensions": {} + }, + "/api/desktop-editor/amount-confirmation": { + "operations": { + "Get": { + "tags": [ + { + "name": "DesktopEditor", + "extensions": {}, + "unresolvedReference": false + } + ], + "summary": "Check the amount of files to open before", + "parameters": [ + { + "unresolvedReference": false, + "name": "f", + "in": 0, + "description": "single or multiple subPaths", + "required": false, + "deprecated": false, + "allowEmptyValue": false, + "explode": false, + "allowReserved": false, + "schema": { + "type": "string", + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": false, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + }, + "examples": {}, + "content": {}, + "extensions": {} + } + ], + "responses": { + "200": { + "description": "bool, true is no confirmation, false is ask confirmation", + "headers": {}, + "content": { + "application/json": { + "schema": { + "type": "boolean", + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": false, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + }, + "examples": {}, + "encoding": {}, + "extensions": {} + } + }, + "links": {}, + "extensions": {}, + "unresolvedReference": false + }, + "401": { + "description": "User unauthorized", + "headers": {}, + "content": {}, + "links": {}, + "extensions": {}, + "unresolvedReference": false + } + }, + "callbacks": {}, + "deprecated": false, + "security": [], + "servers": [], + "extensions": {} + } + }, + "servers": [], + "parameters": [], + "extensions": {} + }, "/api/disk/mkdir": { "operations": { "Post": { @@ -10397,69 +10739,6 @@ "parameters": [], "extensions": {} }, - "/api/trash/detect-to-use-system-trash": { - "operations": { - "Get": { - "tags": [ - { - "name": "Trash", - "extensions": {}, - "unresolvedReference": false - } - ], - "summary": "Is the system trash supported", - "parameters": [], - "responses": { - "200": { - "description": "the item including the updated content", - "headers": {}, - "content": { - "application/json": { - "schema": { - "type": "boolean", - "readOnly": false, - "writeOnly": false, - "allOf": [], - "oneOf": [], - "anyOf": [], - "required": [], - "properties": {}, - "additionalPropertiesAllowed": true, - "enum": [], - "nullable": false, - "deprecated": false, - "extensions": {}, - "unresolvedReference": false - }, - "examples": {}, - "encoding": {}, - "extensions": {} - } - }, - "links": {}, - "extensions": {}, - "unresolvedReference": false - }, - "401": { - "description": "User unauthorized", - "headers": {}, - "content": {}, - "links": {}, - "extensions": {}, - "unresolvedReference": false - } - }, - "callbacks": {}, - "deprecated": false, - "security": [], - "servers": [], - "extensions": {} - } - }, - "servers": [], - "parameters": [], - "extensions": {} - }, "/api/trash/move-to-trash": { "operations": { "Post": { @@ -10470,7 +10749,7 @@ "unresolvedReference": false } ], - "summary": "(beta) Move a file to the trash", + "summary": "Move a file to the trash", "parameters": [ { "unresolvedReference": false, @@ -11969,7 +12248,94 @@ "extensions": {}, "unresolvedReference": false }, - "syncOnStartup": { + "syncOnStartup": { + "type": "boolean", + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": true, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + }, + "importIgnore": { + "type": "array", + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "items": { + "type": "string", + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": false, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + }, + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": true, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + }, + "videoUseLocalTime": { + "type": "array", + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "items": { + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": false, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false, + "reference": { + "type": 0, + "id": "CameraMakeModel", + "isExternal": false, + "isLocal": true, + "referenceV3": "#/components/schemas/CameraMakeModel", + "referenceV2": "#/definitions/CameraMakeModel" + } + }, + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": true, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + }, + "useLocalDesktop": { "type": "boolean", "readOnly": false, "writeOnly": false, @@ -11985,7 +12351,7 @@ "extensions": {}, "unresolvedReference": false }, - "importIgnore": { + "defaultDesktopEditor": { "type": "array", "readOnly": false, "writeOnly": false, @@ -11994,7 +12360,6 @@ "anyOf": [], "required": [], "items": { - "type": "string", "readOnly": false, "writeOnly": false, "allOf": [], @@ -12007,7 +12372,15 @@ "nullable": false, "deprecated": false, "extensions": {}, - "unresolvedReference": false + "unresolvedReference": false, + "reference": { + "type": 0, + "id": "AppSettingsDefaultEditorApplication", + "isExternal": false, + "isLocal": true, + "referenceV3": "#/components/schemas/AppSettingsDefaultEditorApplication", + "referenceV2": "#/definitions/AppSettingsDefaultEditorApplication" + } }, "properties": {}, "additionalPropertiesAllowed": true, @@ -12017,47 +12390,32 @@ "extensions": {}, "unresolvedReference": false }, - "videoUseLocalTime": { - "type": "array", + "desktopCollectionsOpen": { "readOnly": false, "writeOnly": false, "allOf": [], "oneOf": [], "anyOf": [], "required": [], - "items": { - "readOnly": false, - "writeOnly": false, - "allOf": [], - "oneOf": [], - "anyOf": [], - "required": [], - "properties": {}, - "additionalPropertiesAllowed": true, - "enum": [], - "nullable": false, - "deprecated": false, - "extensions": {}, - "unresolvedReference": false, - "reference": { - "type": 0, - "id": "CameraMakeModel", - "isExternal": false, - "isLocal": true, - "referenceV3": "#/components/schemas/CameraMakeModel", - "referenceV2": "#/definitions/CameraMakeModel" - } - }, "properties": {}, "additionalPropertiesAllowed": true, "enum": [], - "nullable": true, + "nullable": false, "deprecated": false, "extensions": {}, - "unresolvedReference": false + "unresolvedReference": false, + "reference": { + "type": 0, + "id": "RawJpegMode", + "isExternal": false, + "isLocal": true, + "referenceV3": "#/components/schemas/RawJpegMode", + "referenceV2": "#/definitions/RawJpegMode" + } }, - "useLocalDesktop": { - "type": "boolean", + "desktopEditorAmountBeforeConfirmation": { + "type": "integer", + "format": "int32", "readOnly": false, "writeOnly": false, "allOf": [], @@ -12250,6 +12608,78 @@ "extensions": {}, "unresolvedReference": false }, + "AppSettingsDefaultEditorApplication": { + "type": "object", + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": { + "imageFormats": { + "type": "array", + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "items": { + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": false, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false, + "reference": { + "type": 0, + "id": "ImageFormat", + "isExternal": false, + "isLocal": true, + "referenceV3": "#/components/schemas/ImageFormat", + "referenceV2": "#/definitions/ImageFormat" + } + }, + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": true, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + }, + "applicationPath": { + "type": "string", + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": true, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + } + }, + "additionalPropertiesAllowed": false, + "enum": [], + "nullable": false, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + }, "AppSettingsKeyValue": { "type": "object", "readOnly": false, @@ -14484,6 +14914,150 @@ "extensions": {}, "unresolvedReference": false }, + "PathImageFormatExistsAppPathModel": { + "type": "object", + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": { + "subPath": { + "type": "string", + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": true, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + }, + "fullFilePath": { + "type": "string", + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": true, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + }, + "imageFormat": { + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": false, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false, + "reference": { + "type": 0, + "id": "ImageFormat", + "isExternal": false, + "isLocal": true, + "referenceV3": "#/components/schemas/ImageFormat", + "referenceV2": "#/definitions/ImageFormat" + } + }, + "status": { + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": false, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false, + "reference": { + "type": 0, + "id": "ExifStatus", + "isExternal": false, + "isLocal": true, + "referenceV3": "#/components/schemas/ExifStatus", + "referenceV2": "#/definitions/ExifStatus" + } + }, + "appPath": { + "type": "string", + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [], + "nullable": true, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + } + }, + "additionalPropertiesAllowed": false, + "enum": [], + "nullable": false, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + }, + "RawJpegMode": { + "type": "integer", + "format": "int32", + "readOnly": false, + "writeOnly": false, + "allOf": [], + "oneOf": [], + "anyOf": [], + "required": [], + "properties": {}, + "additionalPropertiesAllowed": true, + "enum": [ + { + "primitiveType": 0, + "anyType": 0, + "value": 0 + }, + { + "primitiveType": 0, + "anyType": 0, + "value": 1 + }, + { + "primitiveType": 0, + "anyType": 0, + "value": 2 + } + ], + "nullable": false, + "deprecated": false, + "extensions": {}, + "unresolvedReference": false + }, "RelativeObjects": { "type": "object", "readOnly": false, From 7202680a39ecdce2937489417e1841cde1d961ef Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 21 Feb 2024 21:10:57 +0100 Subject: [PATCH 067/125] add imageformat to directory && add UI to open files --- .../desktop-editor/amount-confirmation.json | 1 + .../mock/api/desktop-editor/open.json | 9 + starsky-tools/mock/api/env/features.json | 2 +- starsky-tools/mock/set-router.js | 84 +++-- .../Service/OpenEditorPreflight.cs | 5 + .../starsky.feature.import/Services/Import.cs | 1 + .../Helpers/StatusCodesHelper.cs | 18 +- .../Query/QuerySingleItem.cs | 1 + .../Helpers/ExtensionRolesHelper.cs | 5 +- .../SyncServices/SyncFolder.cs | 1 + .../WatcherHelpers/SyncWatcherConnector.cs | 2 + .../Controllers/DesktopEditorController.cs | 4 +- starsky/starsky/Controllers/DiskController.cs | 5 +- ...menu-option-desktop-editor-open.spec.tsx__ | 190 +++++++++++ ...enu-option-desktop-editor-open.stories.tsx | 52 +++ .../menu-option-desktop-editor-open.tsx | 205 ++++++++++++ .../organisms/menu-archive/menu-archive.tsx | 8 + ...sktop-editor-open-confirmation.stories.tsx | 29 ++ ...modal-desktop-editor-open-confirmation.tsx | 132 ++++++++ .../clientapp/src/interfaces/IEnvFeatures.ts | 1 + .../src/localization/localization.json | 20 ++ .../starsky/clientapp/src/shared/url-query.ts | 8 + .../Services/DeleteItemTest.cs | 307 ++++++++++-------- 23 files changed, 917 insertions(+), 173 deletions(-) create mode 100644 starsky-tools/mock/api/desktop-editor/amount-confirmation.json create mode 100644 starsky-tools/mock/api/desktop-editor/open.json create mode 100644 starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open/_menu-option-desktop-editor-open.spec.tsx__ create mode 100644 starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open/menu-option-desktop-editor-open.stories.tsx create mode 100644 starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open/menu-option-desktop-editor-open.tsx create mode 100644 starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-confirmation/modal-desktop-editor-open-confirmation.stories.tsx create mode 100644 starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-confirmation/modal-desktop-editor-open-confirmation.tsx diff --git a/starsky-tools/mock/api/desktop-editor/amount-confirmation.json b/starsky-tools/mock/api/desktop-editor/amount-confirmation.json new file mode 100644 index 0000000000..f32a5804e2 --- /dev/null +++ b/starsky-tools/mock/api/desktop-editor/amount-confirmation.json @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/starsky-tools/mock/api/desktop-editor/open.json b/starsky-tools/mock/api/desktop-editor/open.json new file mode 100644 index 0000000000..61ad49a945 --- /dev/null +++ b/starsky-tools/mock/api/desktop-editor/open.json @@ -0,0 +1,9 @@ +[ + { + "subPath": "/20221029_101722_DSC05623.arw", + "fullFilePath": "/data/testcontent//20221029_101722_DSC05623.arw", + "imageFormat": 12, + "status": 8, + "appPath": "" + } +] diff --git a/starsky-tools/mock/api/env/features.json b/starsky-tools/mock/api/env/features.json index 4f38575c57..ec7f18de73 100644 --- a/starsky-tools/mock/api/env/features.json +++ b/starsky-tools/mock/api/env/features.json @@ -1 +1 @@ -{"systemTrashEnabled":false,"useLocalDesktop":false, "openEditorEnabled": false} \ No newline at end of file +{"systemTrashEnabled":false,"useLocalDesktop":false, "openEditorEnabled": true} \ No newline at end of file diff --git a/starsky-tools/mock/set-router.js b/starsky-tools/mock/set-router.js index 1f587ebe1a..3b7450c507 100644 --- a/starsky-tools/mock/set-router.js +++ b/starsky-tools/mock/set-router.js @@ -1,43 +1,45 @@ const express = require("express"); const path = require("path"); -var apiAccountChangeSecretIndex = require("./api/account/change-secret/index.json"); -var apiAccountPermissionsIndex = require("./api/account/permissions/index.json"); +const apiAccountChangeSecretIndex = require("./api/account/change-secret/index.json"); +const apiAccountPermissionsIndex = require("./api/account/permissions/index.json"); -var accountStatus = require("./api/account/status/index.json"); -var apiHealthDetails = require("./api/health/details/index.json"); -var apiHealthCheckForUpdates = require("./api/health/check-for-updates/index.json"); -var apiGeoReverseLookup = require("./api/geo-reverse-lookup/index.json"); +const accountStatus = require("./api/account/status/index.json"); +const apiHealthDetails = require("./api/health/details/index.json"); +const apiHealthCheckForUpdates = require("./api/health/check-for-updates/index.json"); +const apiGeoReverseLookup = require("./api/geo-reverse-lookup/index.json"); -var apiIndexIndex = require("./api/index/index.json"); -var apiIndex__Starsky = require("./api/index/__starsky.json"); -var apiIndex0001 = require("./api/index/0001.json"); -var apiIndex0001_toggleDeleted = require("./api/index/0001_toggleDeleted.json"); +const apiIndexIndex = require("./api/index/index.json"); +const apiIndex__Starsky = require("./api/index/__starsky.json"); +const apiIndex0001 = require("./api/index/0001.json"); +const apiIndex0001_toggleDeleted = require("./api/index/0001_toggleDeleted.json"); -var apiIndex__Starsky01dif = require("./api/index/__starsky_01-dif.json"); -var apiIndex__Starsky01difColorclass0 = require("./api/index/__starsky_01-dif_colorclass0.json"); +const apiIndex__Starsky01dif = require("./api/index/__starsky_01-dif.json"); +const apiIndex__Starsky01difColorclass0 = require("./api/index/__starsky_01-dif_colorclass0.json"); -var apiIndex__Starsky01dif20180101170001 = require("./api/index/__starsky_01-dif-2018.01.01.17.00.01.json"); +const apiIndex__Starsky01dif20180101170001 = require("./api/index/__starsky_01-dif-2018.01.01.17.00.01.json"); -var apiInfo__testJpg = require("./api/info/test.jpg.json"); +const apiInfo__testJpg = require("./api/info/test.jpg.json"); -var apiSearchTrash = require("./api/search/trash/index.json"); -var apiSearch = require("./api/search/index.json"); -var apiSearchTest = require("./api/search/test.json"); -var apiSearchTest1 = require("./api/search/test1.json"); -var apiUpdate__Starsky01dif20180101170001_Deleted = require("./api/update/__starsky_01-dif-2018.01.01.17.00.01_Deleted.json"); -var apiUpdate__Starsky01dif20180101170001_Ok = require("./api/update/__starsky_01-dif-2018.01.01.17.00.01_Ok.json"); +const apiSearchTrash = require("./api/search/trash/index.json"); +const apiSearch = require("./api/search/index.json"); +const apiSearchTest = require("./api/search/test.json"); +const apiSearchTest1 = require("./api/search/test1.json"); +const apiUpdate__Starsky01dif20180101170001_Deleted = require("./api/update/__starsky_01-dif-2018.01.01.17.00.01_Deleted.json"); +const apiUpdate__Starsky01dif20180101170001_Ok = require("./api/update/__starsky_01-dif-2018.01.01.17.00.01_Ok.json"); -var apiEnvIndex = require("./api/env/index.json"); -var apiEnvFeatures = require("./api/env/features.json"); +const apiEnvIndex = require("./api/env/index.json"); +const apiEnvFeatures = require("./api/env/features.json"); -var apiPublishIndex = require("./api/publish/index.json"); -var apiPublishCreateIndex = require("./api/publish/create/index.json"); +const apiPublishIndex = require("./api/publish/index.json"); +const apiPublishCreateIndex = require("./api/publish/create/index.json"); -var githubComReposQdrawStarskyReleaseIndex = require("./github.com/repos/qdraw/starsky/releases/index.json"); +const apiDeskopEditorOpen = require("./api/desktop-editor/open.json"); + +const githubComReposQdrawStarskyReleaseIndex = require("./github.com/repos/qdraw/starsky/releases/index.json"); function setRouter(app, isStoryBook = false) { - var prefix = "/starsky"; + const prefix = "/starsky"; app.use( prefix + "/api/thumbnail", @@ -59,7 +61,7 @@ function setRouter(app, isStoryBook = false) { res.json(accountStatus); }); - var isChangePasswordSuccess = false; + let isChangePasswordSuccess = false; app.post(prefix + "/api/account/change-secret/", (req, res) => { console.log(req.body); @@ -159,7 +161,7 @@ function setRouter(app, isStoryBook = false) { return res.json("not found"); }); - var isDeleted = true; + let isDeleted = true; app.post(prefix + "/api/update", (req, res) => { if (!req.body) { res.statusCode = 500; @@ -248,6 +250,30 @@ function setRouter(app, isStoryBook = false) { return res.json(apiEnvIndex); }); + app.post(prefix + "/api/desktop-editor/amount-confirmation", (req, res) => { + if (!req.body) { + return res.json("no body ~ the normal api does ignore it"); + } + console.log(`amount-confirmation ${req.body.f}`); + + return res.json(req.body.f !== "/true.jpg"); + }); + + app.post(prefix + "/api/desktop-editor/open", (req, res) => { + if (!req.body) { + return res.json("no body ~ the normal api does ignore it"); + } + console.log(`open ${req.body.f}`); + + if (req.body.f === "/true.jpg") { + res.statusCode = 400; + res.json() + return + } + + return res.json(apiDeskopEditorOpen); + }); + app.get(prefix + "/api/health/application-insights", (req, res) => { res.set("Content-Type", "application/javascript"); return res.send(""); @@ -282,7 +308,7 @@ function setRouter(app, isStoryBook = false) { }); // Simulate waiting - var fakeLoading = {}; + let fakeLoading = {}; app.get(prefix + "/export/zip/:id", (req, res) => { if (!fakeLoading[req.params.id]) { fakeLoading[req.params.id] = 0; diff --git a/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs b/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs index 3b6c606fa3..94687db487 100644 --- a/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs +++ b/starsky/starsky.feature.desktop/Service/OpenEditorPreflight.cs @@ -63,6 +63,11 @@ private string GetDesktopEditorPath(ExtensionRolesHelper.ImageFormat imageFormat var appPath = appSettingsDefaultEditor?.ApplicationPath ?? string.Empty; + if ( string.IsNullOrEmpty(appPath) ) + { + return string.Empty; + } + // Under Mac OS the ApplicationPath is a .app folder // Under Windows the ApplicationPath is a .exe file if ( _hostFileSystem.IsFolderOrFile(appPath) != diff --git a/starsky/starsky.feature.import/Services/Import.cs b/starsky/starsky.feature.import/Services/Import.cs index a0104c7b81..8a00bdaf2a 100644 --- a/starsky/starsky.feature.import/Services/Import.cs +++ b/starsky/starsky.feature.import/Services/Import.cs @@ -907,6 +907,7 @@ private async Task CreateNewDatabaseDirectory(string parentPath) { AddToDatabase = DateTime.UtcNow, IsDirectory = true, + ImageFormat = ExtensionRolesHelper.ImageFormat.directory, ColorClass = ColorClassParser.Color.None }; diff --git a/starsky/starsky.foundation.database/Helpers/StatusCodesHelper.cs b/starsky/starsky.foundation.database/Helpers/StatusCodesHelper.cs index b43520434b..abc6aff159 100644 --- a/starsky/starsky.foundation.database/Helpers/StatusCodesHelper.cs +++ b/starsky/starsky.foundation.database/Helpers/StatusCodesHelper.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using starsky.foundation.database.Models; +using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Models; namespace starsky.foundation.database.Helpers @@ -16,7 +17,8 @@ public StatusCodesHelper(AppSettings appSettings) public FileIndexItem.ExifStatus IsReadOnlyStatus(FileIndexItem fileIndexItem) { - if ( fileIndexItem.IsDirectory == true && _appSettings.IsReadOnly(fileIndexItem.FilePath!) ) + if ( fileIndexItem.IsDirectory == true && + _appSettings.IsReadOnly(fileIndexItem.FilePath!) ) { return FileIndexItem.ExifStatus.DirReadOnly; } @@ -53,13 +55,16 @@ public FileIndexItem.ExifStatus IsReadOnlyStatus(DetailView? detailView) public static FileIndexItem.ExifStatus IsDeletedStatus(FileIndexItem? fileIndexItem) { - return fileIndexItem?.Tags != null && fileIndexItem.Tags.Contains(TrashKeyword.TrashKeywordString) ? - FileIndexItem.ExifStatus.Deleted : FileIndexItem.ExifStatus.Default; + return fileIndexItem?.Tags != null && + fileIndexItem.Tags.Contains(TrashKeyword.TrashKeywordString) + ? FileIndexItem.ExifStatus.Deleted + : FileIndexItem.ExifStatus.Default; } public static FileIndexItem.ExifStatus IsDeletedStatus(DetailView? detailView) { - if ( !string.IsNullOrEmpty(detailView?.FileIndexItem?.Tags) && detailView.FileIndexItem.Tags.Contains(TrashKeyword.TrashKeywordString) ) + if ( !string.IsNullOrEmpty(detailView?.FileIndexItem?.Tags) && + detailView.FileIndexItem.Tags.Contains(TrashKeyword.TrashKeywordString) ) { return FileIndexItem.ExifStatus.Deleted; } @@ -83,6 +88,7 @@ public static bool ReturnExifStatusError(FileIndexItem statusModel, { case FileIndexItem.ExifStatus.DirReadOnly: statusModel.IsDirectory = true; + statusModel.ImageFormat = ExtensionRolesHelper.ImageFormat.directory; statusModel.Status = FileIndexItem.ExifStatus.DirReadOnly; fileIndexResultsList.Add(statusModel); return true; @@ -107,6 +113,7 @@ public static bool ReturnExifStatusError(FileIndexItem statusModel, fileIndexResultsList.Add(statusModel); return true; } + return false; } @@ -120,6 +127,7 @@ public static bool ReadonlyDenied(FileIndexItem statusModel, fileIndexResultsList.Add(statusModel); return true; } + return false; } @@ -132,7 +140,5 @@ public static void ReadonlyAllowed(FileIndexItem statusModel, statusModel.Status = FileIndexItem.ExifStatus.ReadOnly; fileIndexResultsList.Add(statusModel); } - - } } diff --git a/starsky/starsky.foundation.database/Query/QuerySingleItem.cs b/starsky/starsky.foundation.database/Query/QuerySingleItem.cs index 964475a218..f4d83ca5d5 100644 --- a/starsky/starsky.foundation.database/Query/QuerySingleItem.cs +++ b/starsky/starsky.foundation.database/Query/QuerySingleItem.cs @@ -119,6 +119,7 @@ public partial class Query if ( currentFileIndexItem.IsDirectory == true ) { currentFileIndexItem.CollectionPaths = new List { singleItemDbPath }; + return new DetailView { IsDirectory = true, diff --git a/starsky/starsky.foundation.platform/Helpers/ExtensionRolesHelper.cs b/starsky/starsky.foundation.platform/Helpers/ExtensionRolesHelper.cs index 8c23c89da1..e65a253f58 100644 --- a/starsky/starsky.foundation.platform/Helpers/ExtensionRolesHelper.cs +++ b/starsky/starsky.foundation.platform/Helpers/ExtensionRolesHelper.cs @@ -372,7 +372,10 @@ public enum ImageFormat mp4 = 50, // archives - zip = 60 + zip = 60, + + // folder + directory = 1000 } diff --git a/starsky/starsky.foundation.sync/SyncServices/SyncFolder.cs b/starsky/starsky.foundation.sync/SyncServices/SyncFolder.cs index 98339af010..f44ea093c3 100644 --- a/starsky/starsky.foundation.sync/SyncServices/SyncFolder.cs +++ b/starsky/starsky.foundation.sync/SyncServices/SyncFolder.cs @@ -145,6 +145,7 @@ internal async Task CompareFolderListAndFixMissingFolders(List subPaths, await _query.AddItemAsync(new FileIndexItem(path) { IsDirectory = true, + ImageFormat = ExtensionRolesHelper.ImageFormat.directory, AddToDatabase = DateTime.UtcNow, ColorClass = ColorClassParser.Color.None }); diff --git a/starsky/starsky.foundation.sync/WatcherHelpers/SyncWatcherConnector.cs b/starsky/starsky.foundation.sync/WatcherHelpers/SyncWatcherConnector.cs index 39d619fcff..97039e6441 100644 --- a/starsky/starsky.foundation.sync/WatcherHelpers/SyncWatcherConnector.cs +++ b/starsky/starsky.foundation.sync/WatcherHelpers/SyncWatcherConnector.cs @@ -13,6 +13,7 @@ using starsky.foundation.database.Models; using starsky.foundation.database.Query; using starsky.foundation.platform.Enums; +using starsky.foundation.platform.Helpers; using starsky.foundation.platform.Interfaces; using starsky.foundation.platform.JsonConverter; using starsky.foundation.platform.Models; @@ -114,6 +115,7 @@ private async Task> SyncTaskInternal( syncData.Add(new FileIndexItem(_appSettings.FullPathToDatabaseStyle(fullFilePath)) { IsDirectory = true, + ImageFormat = ExtensionRolesHelper.ImageFormat.directory, Status = FileIndexItem.ExifStatus.NotFoundSourceMissing }); diff --git a/starsky/starsky/Controllers/DesktopEditorController.cs b/starsky/starsky/Controllers/DesktopEditorController.cs index 6b57335577..7b20b63107 100644 --- a/starsky/starsky/Controllers/DesktopEditorController.cs +++ b/starsky/starsky/Controllers/DesktopEditorController.cs @@ -26,7 +26,7 @@ public DesktopEditorController(IOpenEditorDesktopService openEditorDesktopServic /// returns a list of items from the database /// subPath not found in the database /// User unauthorized - [HttpGet("/api/desktop-editor/open")] + [HttpPost("/api/desktop-editor/open")] [Produces("application/json")] [ProducesResponseType(typeof(List), 200)] [ProducesResponseType(typeof(List), 204)] @@ -59,7 +59,7 @@ public async Task OpenAsync( /// /// bool, true is no confirmation, false is ask confirmation /// User unauthorized - [HttpGet("/api/desktop-editor/amount-confirmation")] + [HttpPost("/api/desktop-editor/amount-confirmation")] [Produces("application/json")] [ProducesResponseType(typeof(bool), 200)] [ProducesResponseType(401)] diff --git a/starsky/starsky/Controllers/DiskController.cs b/starsky/starsky/Controllers/DiskController.cs index d7b12c3887..e8c8666dc4 100644 --- a/starsky/starsky/Controllers/DiskController.cs +++ b/starsky/starsky/Controllers/DiskController.cs @@ -74,7 +74,10 @@ public async Task Mkdir(string f) continue; } - await _query.AddItemAsync(new FileIndexItem(subPath) { IsDirectory = true }); + await _query.AddItemAsync(new FileIndexItem(subPath) + { + IsDirectory = true, ImageFormat = ExtensionRolesHelper.ImageFormat.directory + }); // add to fs _iStorage.CreateDirectory(subPath); diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open/_menu-option-desktop-editor-open.spec.tsx__ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open/_menu-option-desktop-editor-open.spec.tsx__ new file mode 100644 index 0000000000..be0f6790f8 --- /dev/null +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open/_menu-option-desktop-editor-open.spec.tsx__ @@ -0,0 +1,190 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import { act } from "react-dom/test-utils"; +import localization from "../../../localization/localization.json"; +import * as Modal from "../../atoms/modal/modal"; +import * as ModalMoveFolderToTrash from "../../organisms/modal-move-folder-to-trash/modal-move-folder-to-trash"; +import MenuOptionMoveFolderToTrash from "./menu-option-move-folder-to-trash"; + +describe("MenuOptionMoveFolderToTrash", () => { + it("renders the menu option correctly", () => { + render( + + ); + + expect(screen.getByText(localization.MessageMoveCurrentFolderToTrash.en)).toBeTruthy(); + }); + + it("opens the modal when the menu option is clicked", () => { + jest + .spyOn(ModalMoveFolderToTrash, "default") + .mockImplementationOnce(() =>
); + + render( + + ); + + const menuOption = screen.getByTestId("move-folder-to-trash"); + fireEvent.click(menuOption); + + expect(screen.getByTestId("modal-move-folder-to-trash")).toBeTruthy(); + }); + + it("opens the modal when the menu option is keyDowned", () => { + jest + .spyOn(ModalMoveFolderToTrash, "default") + .mockImplementationOnce(() =>
); + + render( + + ); + + const menuOption = screen.getByTestId("move-folder-to-trash"); + fireEvent.keyDown(menuOption, { key: "Enter" }); + + expect(screen.getByTestId("modal-move-folder-to-trash")).toBeTruthy(); + }); + + it("not opens the modal when the menu option is keyDowned but wrong key so ignored", () => { + jest + .spyOn(ModalMoveFolderToTrash, "default") + .mockImplementationOnce(() =>
); + + render( + + ); + + const menuOption = screen.getByTestId("move-folder-to-trash"); + fireEvent.keyDown(menuOption, { key: "Tab" }); + + expect(screen.queryByTestId("modal-move-folder-to-trash")).toBeFalsy(); + }); + + it("opens the modal when the menu option is clicked 1", () => { + console.log("----------"); + + const modalSpy = jest.spyOn(Modal, "default").mockImplementationOnce((props) => { + act(() => { + props.handleExit(); + }); + return <>{props.children}; + }); + + jest.spyOn(ModalMoveFolderToTrash, "default").mockImplementationOnce((props) => { + act(() => { + props.handleExit(); + }); + return <>; + }); + + const setEnableMoreMenuSpy = jest.fn(); + render( + + ); + + const menuOption = screen.getByTestId("move-folder-to-trash"); + fireEvent.click(menuOption); + + expect(screen.getByTestId("move-folder-to-trash")).toBeTruthy(); + + expect(setEnableMoreMenuSpy).toHaveBeenCalledTimes(1); + expect(setEnableMoreMenuSpy).toHaveBeenCalledWith(false); + + expect(modalSpy).toHaveBeenCalledTimes(0); + }); + + it("opens the modal when the menu option is keyDown tab so skip", () => { + console.log("----------"); + + const modalSpy = jest.spyOn(Modal, "default").mockImplementationOnce((props) => { + act(() => { + props.handleExit(); + }); + return <>{props.children}; + }); + + jest.spyOn(ModalMoveFolderToTrash, "default").mockImplementationOnce((props) => { + act(() => { + props.handleExit(); + }); + return <>; + }); + + const setEnableMoreMenuSpy = jest.fn(); + render( + + ); + + const menuOption = screen.getByTestId("move-folder-to-trash"); + fireEvent.keyDown(menuOption, { key: "Tab" }); + + expect(screen.getByTestId("move-folder-to-trash")).toBeTruthy(); + + expect(setEnableMoreMenuSpy).toHaveBeenCalledTimes(0); + + expect(modalSpy).toHaveBeenCalledTimes(0); + }); + + it("opens the modal when the menu option is keyDown enter 1", () => { + console.log("----------"); + + const modalSpy = jest.spyOn(Modal, "default").mockImplementationOnce((props) => { + act(() => { + props.handleExit(); + }); + return <>{props.children}; + }); + + jest.spyOn(ModalMoveFolderToTrash, "default").mockImplementationOnce((props) => { + act(() => { + props.handleExit(); + }); + return <>; + }); + + const setEnableMoreMenuSpy = jest.fn(); + render( + + ); + + const menuOption = screen.getByTestId("move-folder-to-trash"); + fireEvent.keyDown(menuOption, { key: "Enter" }); + + expect(screen.getByTestId("move-folder-to-trash")).toBeTruthy(); + + expect(setEnableMoreMenuSpy).toHaveBeenCalledTimes(1); + expect(setEnableMoreMenuSpy).toHaveBeenCalledWith(false); + + expect(modalSpy).toHaveBeenCalledTimes(0); + }); +}); diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open/menu-option-desktop-editor-open.stories.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open/menu-option-desktop-editor-open.stories.tsx new file mode 100644 index 0000000000..9b93a9f3de --- /dev/null +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open/menu-option-desktop-editor-open.stories.tsx @@ -0,0 +1,52 @@ +import MoreMenu from "../../atoms/more-menu/more-menu"; +import MenuOptionDesktopEditorOpen from "./menu-option-desktop-editor-open"; + +export default { + title: "components/molecules/menu-option-desktop-editor-open" +}; + +export const Default = () => { + return ( + {}}> + + + ); +}; + +Default.storyName = "default (no dialog)"; + +export const Case2 = () => { + return ( + {}}> + + + ); +}; + +Case2.storyName = "with dialog"; diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open/menu-option-desktop-editor-open.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open/menu-option-desktop-editor-open.tsx new file mode 100644 index 0000000000..f769f0260e --- /dev/null +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open/menu-option-desktop-editor-open.tsx @@ -0,0 +1,205 @@ +import React, { memo, useState } from "react"; +import useFetch from "../../../hooks/use-fetch"; +import useGlobalSettings from "../../../hooks/use-global-settings"; +import useHotKeys from "../../../hooks/use-keyboard/use-hotkeys"; +import useLocation from "../../../hooks/use-location/use-location"; +import { IArchiveProps } from "../../../interfaces/IArchiveProps"; +import { PageType } from "../../../interfaces/IDetailView"; +import { IEnvFeatures } from "../../../interfaces/IEnvFeatures"; +import localization from "../../../localization/localization.json"; +import FetchPost from "../../../shared/fetch/fetch-post"; +import { Language } from "../../../shared/language"; +import { URLPath } from "../../../shared/url-path"; +import { UrlQuery } from "../../../shared/url-query"; +import MenuOption from "../../atoms/menu-option/menu-option"; +import Notification, { NotificationType } from "../../atoms/notification/notification"; +import ModalDesktopEditorOpenConfirmation from "../../organisms/modal-desktop-editor-open-confirmation/modal-desktop-editor-open-confirmation"; + +interface IMenuOptionDesktopEditorOpenProps { + state: IArchiveProps; + select: string[]; + isReadOnly: boolean; + setEnableMoreMenu?: React.Dispatch>; +} + +async function openDesktop( + select: string[], + collections: boolean, + state: IArchiveProps, + setIsError: React.Dispatch>, + messageDesktopEditorUnableToOpen: string +) { + const toDesktopOpenList = new URLPath().MergeSelectFileIndexItem(select, state.fileIndexItems); + if (!toDesktopOpenList) return; + const selectParams = new URLPath().ArrayToCommaSeparatedStringOneParent(toDesktopOpenList, ""); + const urlOpen = new UrlQuery().UrlApiDesktopEditorOpen(); + + const bodyParams = new URLSearchParams(); + bodyParams.append("f", selectParams); + bodyParams.append("collections", collections.toString()); + + const openDesktopResult = await FetchPost(urlOpen, bodyParams.toString()); + if (openDesktopResult.statusCode >= 300) { + setIsError(messageDesktopEditorUnableToOpen); + } +} + +async function startMenuOptionDesktopEditorOpen( + select: string[], + collections: boolean, + state: IArchiveProps, + setIsError: React.Dispatch>, + messageDesktopEditorUnableToOpen: string, + setModalConfirmationOpenFiles: (value: React.SetStateAction) => void +) { + const toDesktopOpenList = new URLPath().MergeSelectFileIndexItem(select, state.fileIndexItems); + if (!toDesktopOpenList) return; + const selectParams = new URLPath().ArrayToCommaSeparatedStringOneParent(toDesktopOpenList, ""); + const urlCheck = new UrlQuery().UrlApiDesktopEditorOpenAmountConfirmationChecker(); + + const bodyParams = new URLSearchParams(); + bodyParams.append("f", selectParams); + + const openWithoutConformationResult = (await FetchPost(urlCheck, bodyParams.toString())).data; + if (openWithoutConformationResult === false) { + setModalConfirmationOpenFiles(true); + return; + } + await openDesktop(select, collections, state, setIsError, messageDesktopEditorUnableToOpen); +} + +const MenuOptionDesktopEditorOpen: React.FunctionComponent = + memo(({ state, select, isReadOnly }) => { + const featuresResult = useFetch(new UrlQuery().UrlApiFeaturesAppSettings(), "get"); + const dataFeatures = featuresResult?.data as IEnvFeatures | undefined; + const history = useLocation(); + + // Get language keys + const settings = useGlobalSettings(); + const language = new Language(settings.language); + const MessageDesktopEditorUnableToOpen = language.key( + localization.MessageDesktopEditorUnableToOpen + ); + + // for showing a notification + const [isError, setIsError] = useState(""); + + const [modalConfirmationOpenFiles, setModalConfirmationOpenFiles] = useState(false); + + const isCollections = + state.pageType !== PageType.Search + ? new URLPath().StringToIUrl(history.location.search).collections !== false + : false; + + /** + * Open editor with keys + */ + useHotKeys({ key: "e", ctrlKeyOrMetaKey: true }, () => { + console.log("hi"); + + startMenuOptionDesktopEditorOpen( + select, + isCollections, + state, + setIsError, + MessageDesktopEditorUnableToOpen, + setModalConfirmationOpenFiles + ).then(() => { + // do nothing + }); + }); + + return ( + <> + {/* Modal move folder to trash */} + {modalConfirmationOpenFiles ? ( + { + setModalConfirmationOpenFiles(!modalConfirmationOpenFiles); + }} + select={select} + state={state} + isCollections={isCollections} + setIsLoading={() => {}} + isOpen={modalConfirmationOpenFiles} + /> + ) : null} + + {isError !== "" ? ( + setIsError("")} type={NotificationType.danger}> + {isError} + + ) : null} + + {select.length >= 1 && dataFeatures?.openEditorEnabled === true ? ( + + startMenuOptionDesktopEditorOpen( + select, + isCollections, + state, + setIsError, + MessageDesktopEditorUnableToOpen, + setModalConfirmationOpenFiles + ) + } + localization={ + select.length === 1 + ? localization.MessageDesktopEditorOpenSingleFile + : localization.MessageDesktopEditorOpenMultipleFiles + } + /> + ) : null} + + ); + }); + +export default MenuOptionDesktopEditorOpen; + +// async function moveToTrashSelection() { +// if (!select || isReadOnly) return; + +// const toUndoTrashList = new URLPath().MergeSelectFileIndexItem(select, state.fileIndexItems); + +// if (!toUndoTrashList) return; +// const selectParams = new URLPath().ArrayToCommaSeparatedStringOneParent(toUndoTrashList, ""); +// if (selectParams.length === 0) return; + +// const bodyParams = new URLSearchParams(); +// // noinspection PointlessBooleanExpressionJS +// const collections = new URLPath().StringToIUrl(history.location.search).collections !== false; + +// bodyParams.append("f", selectParams); +// bodyParams.set("Tags", "!delete!"); +// bodyParams.set("append", "true"); +// bodyParams.set("Colorclass", "8"); +// bodyParams.set("collections", collections.toString()); + +// const resultDo = await FetchPost(new UrlQuery().UrlMoveToTrashApi(), bodyParams.toString()); + +// if ( +// resultDo.statusCode === 404 || +// resultDo.statusCode === 400 || +// resultDo.statusCode === 500 || +// resultDo.statusCode === 502 +// ) { +// return; +// } + +// undoSelection(); +// dispatch({ type: "remove", toRemoveFileList: toUndoTrashList }); +// ClearSearchCache(history.location.search); +// // Client side Caching: the order of files in a normal folder has changed +// new FileListCache().CacheCleanEverything(); +// } + +// /** +// * When pressing delete its moved to the trash +// */ +// useHotKeys({ key: "E", ctrlKeyOrMetaKey: true }, () => { +// moveToTrashSelection().then(() => { +// // do nothing +// }); +// }); diff --git a/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx b/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx index 91dc76f904..b3db00ceea 100644 --- a/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx @@ -14,6 +14,7 @@ import HamburgerMenuToggle from "../../atoms/hamburger-menu-toggle/hamburger-men import MenuOptionModal from "../../atoms/menu-option-modal/menu-option-modal"; import MoreMenu from "../../atoms/more-menu/more-menu"; import MenuSearchBar from "../../molecules/menu-inline-search/menu-inline-search"; +import MenuOptionDesktopEditorOpen from "../../molecules/menu-option-desktop-editor-open/menu-option-desktop-editor-open"; import MenuOptionMoveFolderToTrash from "../../molecules/menu-option-move-folder-to-trash/menu-option-move-folder-to-trash"; import MenuOptionMoveToTrash from "../../molecules/menu-option-move-to-trash/menu-option-move-to-trash"; import { MenuOptionSelectionAll } from "../../molecules/menu-option-selection-all/menu-option-selection-all"; @@ -285,6 +286,13 @@ const MenuArchive: React.FunctionComponent = memo(() => { setSelect={setSelect} isReadOnly={readOnly} /> + + ) : null} { + return ( + {}} + isOpen={true} + handleExit={() => {}} + /> + ); +}; + +Default.storyName = "default"; diff --git a/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-confirmation/modal-desktop-editor-open-confirmation.tsx b/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-confirmation/modal-desktop-editor-open-confirmation.tsx new file mode 100644 index 0000000000..52da8e87e5 --- /dev/null +++ b/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-confirmation/modal-desktop-editor-open-confirmation.tsx @@ -0,0 +1,132 @@ +import { useState } from "react"; +import useGlobalSettings from "../../../hooks/use-global-settings"; +import { IArchiveProps } from "../../../interfaces/IArchiveProps"; +import localization from "../../../localization/localization.json"; +import FetchPost from "../../../shared/fetch/fetch-post"; +import { Language } from "../../../shared/language"; +import { URLPath } from "../../../shared/url-path"; +import { UrlQuery } from "../../../shared/url-query"; +import Modal from "../../atoms/modal/modal"; + +interface IModalDesktopEditorOpenConfirmationProps { + isOpen: boolean; + select: Array | undefined; + handleExit(): void; + state: IArchiveProps; + setIsLoading: React.Dispatch>; + isCollections: boolean; +} + +async function OpenDesktop( + select: string[], + collections: boolean, + state: IArchiveProps, + setIsError: React.Dispatch>, + messageDesktopEditorUnableToOpen: string +): Promise { + const toDesktopOpenList = new URLPath().MergeSelectFileIndexItem(select, state.fileIndexItems); + if (!toDesktopOpenList) return false; + const selectParams = new URLPath().ArrayToCommaSeparatedStringOneParent(toDesktopOpenList, ""); + const urlOpen = new UrlQuery().UrlApiDesktopEditorOpen(); + + const bodyParams = new URLSearchParams(); + bodyParams.append("f", selectParams); + bodyParams.append("collections", collections.toString()); + + const openDesktopResult = await FetchPost(urlOpen, bodyParams.toString()); + if (openDesktopResult.statusCode >= 300) { + setIsError(messageDesktopEditorUnableToOpen); + } + return true; +} + +const ModalDesktopEditorOpenConfirmation: React.FunctionComponent< + IModalDesktopEditorOpenConfirmationProps +> = ({ select, handleExit, isOpen, state, isCollections }) => { + // content + const settings = useGlobalSettings(); + const language = new Language(settings.language); + const MessageDesktopEditorConfirmationIntroText = language.key( + localization.MessageDesktopEditorConfirmationIntroText + ); + const MessageDesktopEditorConfirmationHeading = language.key( + localization.MessageDesktopEditorConfirmationHeading + ); + const MessageCancel = language.key(localization.MessageCancel); + const MessageDesktopEditorOpenMultipleFiles = language.key( + localization.MessageDesktopEditorOpenMultipleFiles + ); + const MessageDesktopEditorUnableToOpen = language.key( + localization.MessageDesktopEditorUnableToOpen + ); + + // for showing a notification + const [isError, setIsError] = useState(""); + + return ( + { + handleExit(); + }} + > + <> +
{MessageDesktopEditorConfirmationHeading}
+
+

{MessageDesktopEditorConfirmationIntroText}

+ {isError ? ( + <> +
+
{isError}
+ + ) : null} + + + +
+ +
+ ); +}; + +export default ModalDesktopEditorOpenConfirmation; diff --git a/starsky/starsky/clientapp/src/interfaces/IEnvFeatures.ts b/starsky/starsky/clientapp/src/interfaces/IEnvFeatures.ts index 891a0afd04..647717e375 100644 --- a/starsky/starsky/clientapp/src/interfaces/IEnvFeatures.ts +++ b/starsky/starsky/clientapp/src/interfaces/IEnvFeatures.ts @@ -1,4 +1,5 @@ export interface IEnvFeatures { systemTrashEnabled: boolean; useLocalDesktop: boolean; + openEditorEnabled: boolean; } diff --git a/starsky/starsky/clientapp/src/localization/localization.json b/starsky/starsky/clientapp/src/localization/localization.json index 8b3b9ff07e..e622b625f0 100644 --- a/starsky/starsky/clientapp/src/localization/localization.json +++ b/starsky/starsky/clientapp/src/localization/localization.json @@ -239,6 +239,26 @@ "en": "Close", "nl": "Sluiten" }, + "MessageDesktopEditorOpenSingleFile": { + "en": "Open file", + "nl": "Open bestand" + }, + "MessageDesktopEditorOpenMultipleFiles": { + "en": "Open files", + "nl": "Open bestanden" + }, + "MessageDesktopEditorUnableToOpen": { + "en": "Sorry, something went wrong opening the file", + "nl": "Sorry, er iets misgegaan met het openen van het bestand" + }, + "MessageDesktopEditorConfirmationHeading": { + "en": "Do you really want to edit all of the selected photos?", + "nl": "Wil je echt alle geselecteerde foto's bewerken?" + }, + "MessageDesktopEditorConfirmationIntroText": { + "en": "This prompt is to prevent potential issues such as your computer freezing", + "nl": "Deze melding is bedoeld om te voorkomen dat er eventuele problemen optreden, zoals het vastlopen van je computer." + }, "temp1": { "en": "", "nl": "" diff --git a/starsky/starsky/clientapp/src/shared/url-query.ts b/starsky/starsky/clientapp/src/shared/url-query.ts index d6a84d0e2a..81015ab7cf 100644 --- a/starsky/starsky/clientapp/src/shared/url-query.ts +++ b/starsky/starsky/clientapp/src/shared/url-query.ts @@ -328,6 +328,14 @@ export class UrlQuery { return this.prefix + "/api/env/features?v=0.6.0-beta.2"; }; + public UrlApiDesktopEditorOpenAmountConfirmationChecker = (): string => { + return `${this.prefix}/api/desktop-editor/amount-confirmation`; + }; + + public UrlApiDesktopEditorOpen = (): string => { + return `${this.prefix}/api/desktop-editor/open`; + }; + /** * url create a zip */ diff --git a/starsky/starskytest/starsky.feature.metaupdate/Services/DeleteItemTest.cs b/starsky/starskytest/starsky.feature.metaupdate/Services/DeleteItemTest.cs index 29d2b3997c..ac6309c193 100644 --- a/starsky/starskytest/starsky.feature.metaupdate/Services/DeleteItemTest.cs +++ b/starsky/starskytest/starsky.feature.metaupdate/Services/DeleteItemTest.cs @@ -18,7 +18,7 @@ public async Task Delete_FileNotFound_Ignore() var selectorStorage = new FakeSelectorStorage(new FakeIStorage()); var deleteItem = new DeleteItem(new FakeIQuery(), new AppSettings(), selectorStorage); var result = await deleteItem.DeleteAsync("/not-found", true); - Assert.AreEqual(FileIndexItem.ExifStatus.NotFoundNotInIndex, + Assert.AreEqual(FileIndexItem.ExifStatus.NotFoundNotInIndex, result.FirstOrDefault()?.Status); } @@ -27,188 +27,210 @@ public async Task Delete_NotFoundOnDisk_Ignore() { var selectorStorage = new FakeSelectorStorage(new FakeIStorage()); var fakeQuery = - new FakeIQuery(new List {new FileIndexItem("/exist-in-db.jpg")}); - var deleteItem = new DeleteItem( fakeQuery,new AppSettings(), selectorStorage); + new FakeIQuery(new List { new FileIndexItem("/exist-in-db.jpg") }); + var deleteItem = new DeleteItem(fakeQuery, new AppSettings(), selectorStorage); var result = await deleteItem.DeleteAsync("/exist-in-db.jpg", true); - Assert.AreEqual(FileIndexItem.ExifStatus.NotFoundSourceMissing, + Assert.AreEqual(FileIndexItem.ExifStatus.NotFoundSourceMissing, result.FirstOrDefault()?.Status); } - + [TestMethod] public async Task Delete_ReadOnly_Ignored() { - var selectorStorage = new FakeSelectorStorage(new FakeIStorage(new List{"/"}, - new List{"/readonly/test.jpg"}, new List{FakeCreateAn.CreateAnImage.Bytes.ToArray()})); + var selectorStorage = new FakeSelectorStorage(new FakeIStorage(new List { "/" }, + new List { "/readonly/test.jpg" }, + new List { FakeCreateAn.CreateAnImage.Bytes.ToArray() })); var fakeQuery = - new FakeIQuery(new List {new FileIndexItem("/readonly/test.jpg")}); - var deleteItem = new DeleteItem( fakeQuery,new AppSettings{ReadOnlyFolders = new List{"/readonly"}}, selectorStorage); + new FakeIQuery(new List { new FileIndexItem("/readonly/test.jpg") }); + var deleteItem = new DeleteItem(fakeQuery, + new AppSettings { ReadOnlyFolders = new List { "/readonly" } }, + selectorStorage); var result = await deleteItem.DeleteAsync("/readonly/test.jpg", true); - - Assert.AreEqual(FileIndexItem.ExifStatus.ReadOnly, + + Assert.AreEqual(FileIndexItem.ExifStatus.ReadOnly, result.FirstOrDefault()?.Status); } - + [TestMethod] public async Task Delete_StatusNotDeleted_Ignored() { - var selectorStorage = new FakeSelectorStorage(new FakeIStorage(new List{"/"}, - new List{"/test.jpg"}, new List{FakeCreateAn.CreateAnImage.Bytes.ToArray()})); + var selectorStorage = new FakeSelectorStorage(new FakeIStorage(new List { "/" }, + new List { "/test.jpg" }, + new List { FakeCreateAn.CreateAnImage.Bytes.ToArray() })); var fakeQuery = - new FakeIQuery(new List {new FileIndexItem("/test.jpg")}); - var deleteItem = new DeleteItem( fakeQuery,new AppSettings(), selectorStorage); + new FakeIQuery(new List { new FileIndexItem("/test.jpg") }); + var deleteItem = new DeleteItem(fakeQuery, new AppSettings(), selectorStorage); var result = await deleteItem.DeleteAsync("/test.jpg", true); - - Assert.AreEqual(FileIndexItem.ExifStatus.OperationNotSupported, + + Assert.AreEqual(FileIndexItem.ExifStatus.OperationNotSupported, result.FirstOrDefault()?.Status); } - + [TestMethod] public async Task Delete_IsFileRemoved() { - var storage = new FakeIStorage(new List {"/"}, - new List {"/test.jpg"}, - new List {FakeCreateAn.CreateAnImage.Bytes.ToArray()}); + var storage = new FakeIStorage(new List { "/" }, + new List { "/test.jpg" }, + new List { FakeCreateAn.CreateAnImage.Bytes.ToArray() }); var selectorStorage = new FakeSelectorStorage(storage); var fakeQuery = - new FakeIQuery(new List {new FileIndexItem("/test.jpg") - {Tags = TrashKeyword.TrashKeywordString}}); - var deleteItem = new DeleteItem( fakeQuery,new AppSettings(), selectorStorage); + new FakeIQuery(new List + { + new FileIndexItem("/test.jpg") { Tags = TrashKeyword.TrashKeywordString } + }); + var deleteItem = new DeleteItem(fakeQuery, new AppSettings(), selectorStorage); var result = await deleteItem.DeleteAsync("/test.jpg", true); - - Assert.AreEqual(FileIndexItem.ExifStatus.Ok, + + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, result.FirstOrDefault()?.Status); - + Assert.IsNull(fakeQuery.GetObjectByFilePath("/test.jpg")); Assert.IsFalse(storage.ExistFile("/test.jpg")); } - - + + [TestMethod] public async Task Delete_IsFileRemoved_WithCollection() { - var storage = new FakeIStorage(new List {"/", "/dir"}, - new List {"/dir/test.jpg"}, - new List {FakeCreateAn.CreateAnImage.Bytes.ToArray()}); + var storage = new FakeIStorage(new List { "/", "/dir" }, + new List { "/dir/test.jpg" }, + new List { FakeCreateAn.CreateAnImage.Bytes.ToArray() }); var selectorStorage = new FakeSelectorStorage(storage); var fakeQuery = - new FakeIQuery(new List { - new FileIndexItem("/dir") {IsDirectory = true, Tags = TrashKeyword.TrashKeywordString }, - - new FileIndexItem("/dir/test.jpg") {Tags = TrashKeyword.TrashKeywordString }, - new FileIndexItem("/dir/test.dng") {Tags = TrashKeyword.TrashKeywordString }} + new FakeIQuery(new List + { + new FileIndexItem("/dir") + { + IsDirectory = true, Tags = TrashKeyword.TrashKeywordString + }, + new FileIndexItem("/dir/test.jpg") + { + Tags = TrashKeyword.TrashKeywordString + }, + new FileIndexItem("/dir/test.dng") + { + Tags = TrashKeyword.TrashKeywordString + } + } ); - - var deleteItem = new DeleteItem( fakeQuery,new AppSettings(), selectorStorage); + + var deleteItem = new DeleteItem(fakeQuery, new AppSettings(), selectorStorage); var result = await deleteItem.DeleteAsync("/dir/test.jpg", true); - - Assert.AreEqual(FileIndexItem.ExifStatus.Ok, + + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, result.FirstOrDefault()?.Status); - + Assert.IsNull(fakeQuery.GetObjectByFilePath("/test.jpg")); Assert.IsFalse(storage.ExistFile("/test.jpg")); - - Assert.AreEqual(FileIndexItem.ExifStatus.Ok, + + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, result[1].Status); - + Assert.IsNull(fakeQuery.GetObjectByFilePath("/test.dng")); Assert.IsFalse(storage.ExistFile("/test.dng")); } - + [TestMethod] public async Task Delete_IsJsonSideCarFileRemoved() { - var storage = new FakeIStorage(new List {"/"}, - new List {"/test.jpg","/.starsky.test.jpg.json"}, - new List {FakeCreateAn.CreateAnImage.Bytes.ToArray()}); + var storage = new FakeIStorage(new List { "/" }, + new List { "/test.jpg", "/.starsky.test.jpg.json" }, + new List { FakeCreateAn.CreateAnImage.Bytes.ToArray() }); var selectorStorage = new FakeSelectorStorage(storage); var fakeQuery = - new FakeIQuery(new List {new FileIndexItem("/test.jpg") - {Tags = TrashKeyword.TrashKeywordString}}); - var deleteItem = new DeleteItem( fakeQuery,new AppSettings(), selectorStorage); + new FakeIQuery(new List + { + new FileIndexItem("/test.jpg") { Tags = TrashKeyword.TrashKeywordString } + }); + var deleteItem = new DeleteItem(fakeQuery, new AppSettings(), selectorStorage); var result = await deleteItem.DeleteAsync("/test.jpg", true); - - Assert.AreEqual(FileIndexItem.ExifStatus.Ok, + + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, result.FirstOrDefault()?.Status); - + Assert.IsFalse(storage.ExistFile("/.starsky.test.jpg.json")); } - + [TestMethod] public async Task Delete_IsXmpSideCarFileRemoved() { - var storage = new FakeIStorage(new List {"/"}, - new List {"/test.dng","/test.xmp"}, - new List {FakeCreateAn.CreateAnImage.Bytes.ToArray()}); + var storage = new FakeIStorage(new List { "/" }, + new List { "/test.dng", "/test.xmp" }, + new List { FakeCreateAn.CreateAnImage.Bytes.ToArray() }); var selectorStorage = new FakeSelectorStorage(storage); var fakeQuery = - new FakeIQuery(new List {new FileIndexItem("/test.dng") - {Tags = TrashKeyword.TrashKeywordString}}); - var deleteItem = new DeleteItem( fakeQuery,new AppSettings(), selectorStorage); + new FakeIQuery(new List + { + new FileIndexItem("/test.dng") { Tags = TrashKeyword.TrashKeywordString } + }); + var deleteItem = new DeleteItem(fakeQuery, new AppSettings(), selectorStorage); var result = await deleteItem.DeleteAsync("/test.dng", true); - - Assert.AreEqual(FileIndexItem.ExifStatus.Ok, + + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, result.FirstOrDefault()?.Status); - + Assert.IsFalse(storage.ExistFile("/test.xmp")); } - + [TestMethod] public async Task Delete_IsFolderRemoved() { - var storage = new FakeIStorage(new List {"/test","/"}, - new List (), - new List {FakeCreateAn.CreateAnImage.Bytes.ToArray()}); + var storage = new FakeIStorage(new List { "/test", "/" }, + new List(), + new List { FakeCreateAn.CreateAnImage.Bytes.ToArray() }); var selectorStorage = new FakeSelectorStorage(storage); var fakeQuery = - new FakeIQuery(new List {new FileIndexItem("/test") - {IsDirectory = true, Tags = TrashKeyword.TrashKeywordString}}); - var deleteItem = new DeleteItem( fakeQuery,new AppSettings(), selectorStorage); + new FakeIQuery(new List + { + new FileIndexItem("/test") + { + IsDirectory = true, Tags = TrashKeyword.TrashKeywordString + } + }); + + var deleteItem = new DeleteItem(fakeQuery, new AppSettings(), selectorStorage); var result = await deleteItem.DeleteAsync("/test", true); - - Assert.AreEqual(FileIndexItem.ExifStatus.Ok, + + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, result.FirstOrDefault()?.Status); - + Assert.IsNull(fakeQuery.GetObjectByFilePath("/test")); Assert.IsFalse(storage.ExistFolder("/test")); } - + [TestMethod] public async Task Delete_IsFolderRemoved_IncludingChildFolders() { var storage = new FakeIStorage( - new List - { - "/test", - "/", - "/test/child_folder" - }, - new List {"/test/child_folder/i.jpg"}, - new List - { - FakeCreateAn.CreateAnImage.Bytes.ToArray() - }); + new List { "/test", "/", "/test/child_folder" }, + new List { "/test/child_folder/i.jpg" }, + new List { FakeCreateAn.CreateAnImage.Bytes.ToArray() }); var selectorStorage = new FakeSelectorStorage(storage); var fakeQuery = - new FakeIQuery(new List { - new FileIndexItem("/test"){IsDirectory = true, Tags = TrashKeyword.TrashKeywordString}, - new FileIndexItem("/test/child_folder"){IsDirectory = true}, - new FileIndexItem("/test/child_folder/2"){IsDirectory = true} + new FakeIQuery(new List + { + new FileIndexItem("/test") + { + IsDirectory = true, Tags = TrashKeyword.TrashKeywordString + }, + new FileIndexItem("/test/child_folder") { IsDirectory = true }, + new FileIndexItem("/test/child_folder/2") { IsDirectory = true } }); - var deleteItem = new DeleteItem( fakeQuery,new AppSettings(), selectorStorage); + var deleteItem = new DeleteItem(fakeQuery, new AppSettings(), selectorStorage); var result = await deleteItem.DeleteAsync("/test", true); - - Assert.AreEqual(FileIndexItem.ExifStatus.Ok, + + Assert.AreEqual(FileIndexItem.ExifStatus.Ok, result.FirstOrDefault()?.Status); - - Assert.AreEqual(0,fakeQuery.GetAllFolders().Count); + + Assert.AreEqual(0, fakeQuery.GetAllFolders().Count); Assert.IsNull(fakeQuery.GetObjectByFilePath("/test")); Assert.IsNull(fakeQuery.GetObjectByFilePath("/test/child_folder")); Assert.IsNull(fakeQuery.GetObjectByFilePath("/test/child_folder/2")); @@ -218,18 +240,27 @@ public async Task Delete_IsFolderRemoved_IncludingChildFolders() [TestMethod] public async Task Delete_DirectoryWithChildItems_CollectionsOn() { - var storage = new FakeIStorage(new List {"/test","/"}, - new List {"/test/image.jpg", "/test/image.dng"}, - new List {FakeCreateAn.CreateAnImage.Bytes.ToArray(), - FakeCreateAn.CreateAnImage.Bytes.ToArray()}); + var storage = new FakeIStorage(new List { "/test", "/" }, + new List { "/test/image.jpg", "/test/image.dng" }, + new List + { + FakeCreateAn.CreateAnImage.Bytes.ToArray(), + FakeCreateAn.CreateAnImage.Bytes.ToArray() + }); var selectorStorage = new FakeSelectorStorage(storage); - + var fakeQuery = - new FakeIQuery(new List {new FileIndexItem("/test") - {IsDirectory = true, Tags = TrashKeyword.TrashKeywordString}, new FileIndexItem("/test/image.jpg"), - new FileIndexItem("/test/image.dng")}); - - var deleteItem = new DeleteItem( fakeQuery,new AppSettings(), selectorStorage); + new FakeIQuery(new List + { + new FileIndexItem("/test") + { + IsDirectory = true, Tags = TrashKeyword.TrashKeywordString + }, + new FileIndexItem("/test/image.jpg"), + new FileIndexItem("/test/image.dng") + }); + + var deleteItem = new DeleteItem(fakeQuery, new AppSettings(), selectorStorage); var result = await deleteItem.DeleteAsync("/test", true); Assert.AreEqual(3, result.Count); @@ -240,26 +271,31 @@ public async Task Delete_DirectoryWithChildItems_CollectionsOn() Assert.AreEqual(0, storage.GetAllFilesInDirectoryRecursive("/").Count()); Assert.AreEqual(0, fakeQuery.GetAllRecursive("/").Count); } - + [TestMethod] public async Task Delete_DirectoryWithChildItems_CollectionsOff() { - var storage = new FakeIStorage(new List {"/test","/"}, - new List {"/test/image.jpg", "/test/image.dng"}, - new List {FakeCreateAn.CreateAnImage.Bytes.ToArray(), - FakeCreateAn.CreateAnImage.Bytes.ToArray()}); + var storage = new FakeIStorage(new List { "/test", "/" }, + new List { "/test/image.jpg", "/test/image.dng" }, + new List + { + FakeCreateAn.CreateAnImage.Bytes.ToArray(), + FakeCreateAn.CreateAnImage.Bytes.ToArray() + }); var selectorStorage = new FakeSelectorStorage(storage); - + var fakeQuery = - new FakeIQuery(new List { - new FileIndexItem("/test") { - IsDirectory = true, - Tags = TrashKeyword.TrashKeywordString - }, - new FileIndexItem("/test/image.jpg"), - new FileIndexItem("/test/image.dng")}); - - var deleteItem = new DeleteItem( fakeQuery,new AppSettings(), selectorStorage); + new FakeIQuery(new List + { + new FileIndexItem("/test") + { + IsDirectory = true, Tags = TrashKeyword.TrashKeywordString + }, + new FileIndexItem("/test/image.jpg"), + new FileIndexItem("/test/image.dng") + }); + + var deleteItem = new DeleteItem(fakeQuery, new AppSettings(), selectorStorage); var result = await deleteItem.DeleteAsync("/test", false); Assert.AreEqual(3, result.Count); @@ -268,28 +304,33 @@ public async Task Delete_DirectoryWithChildItems_CollectionsOff() Assert.AreEqual("/test/image.dng", result[2].FilePath); Assert.AreEqual(0, storage.GetAllFilesInDirectoryRecursive("/").Count()); - Assert.AreEqual(0, (await fakeQuery.GetAllRecursiveAsync("/")).Count); + Assert.AreEqual(0, ( await fakeQuery.GetAllRecursiveAsync() ).Count); } [TestMethod] public async Task Delete_ChildDirectories() { - var storage = new FakeIStorage(new List {"/test", "/", "/test/child", "/test/child/child"}, - new List (), + var storage = new FakeIStorage( + new List { "/test", "/", "/test/child", "/test/child/child" }, + new List(), new List()); var selectorStorage = new FakeSelectorStorage(storage); - + var fakeQuery = - new FakeIQuery(new List { - new FileIndexItem("/test") {IsDirectory = true, Tags = TrashKeyword.TrashKeywordString}, - new FileIndexItem("/test/child") {IsDirectory = true}, - new FileIndexItem("/test/child/child") {IsDirectory = true}, + new FakeIQuery(new List + { + new FileIndexItem("/test") + { + IsDirectory = true, Tags = TrashKeyword.TrashKeywordString + }, + new FileIndexItem("/test/child") { IsDirectory = true }, + new FileIndexItem("/test/child/child") { IsDirectory = true }, }); - - var deleteItem = new DeleteItem( fakeQuery,new AppSettings(), selectorStorage); + + var deleteItem = new DeleteItem(fakeQuery, new AppSettings(), selectorStorage); var result = await deleteItem.DeleteAsync("/test", false); - + Assert.AreEqual(3, result.Count); Assert.AreEqual("/test", result[0].FilePath); Assert.AreEqual("/test/child", result[1].FilePath); From 00725a1bf4547e140d29d910398b5f76aa73462b Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 21 Feb 2024 21:19:28 +0100 Subject: [PATCH 068/125] data test --- .../modal-desktop-editor-open-confirmation.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-confirmation/modal-desktop-editor-open-confirmation.tsx b/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-confirmation/modal-desktop-editor-open-confirmation.tsx index 52da8e87e5..d49f678678 100644 --- a/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-confirmation/modal-desktop-editor-open-confirmation.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-confirmation/modal-desktop-editor-open-confirmation.tsx @@ -72,8 +72,10 @@ const ModalDesktopEditorOpenConfirmation: React.FunctionComponent< }} > <> -
{MessageDesktopEditorConfirmationHeading}
-
+
+ {MessageDesktopEditorConfirmationHeading} +
+

{MessageDesktopEditorConfirmationIntroText}

{isError ? ( <> From b03b7f98cbdc644264c9b1bfb599555a16b63f2c Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 21 Feb 2024 21:19:58 +0100 Subject: [PATCH 069/125] prefix --- .../modal-desktop-editor-open-confirmation.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-confirmation/modal-desktop-editor-open-confirmation.tsx b/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-confirmation/modal-desktop-editor-open-confirmation.tsx index d49f678678..3f2090a495 100644 --- a/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-confirmation/modal-desktop-editor-open-confirmation.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-confirmation/modal-desktop-editor-open-confirmation.tsx @@ -85,7 +85,7 @@ const ModalDesktopEditorOpenConfirmation: React.FunctionComponent< ) : null}
/// /// - private static (List, List<(string FullFilePath, string AppPath)>) + internal static (List, List<(string FullFilePath, string AppPath)>) FilterListOpenDefaultEditorAndSpecificEditor( IReadOnlyCollection subPathAndImageFormatList) { diff --git a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs index c447258c5b..b262c5f3fc 100644 --- a/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs +++ b/starsky/starskytest/starsky.feature.desktop/Service/OpenEditorDesktopServiceTest.cs @@ -231,4 +231,136 @@ public void OpenAmountConfirmationChecker_1File() var result = service.OpenAmountConfirmationChecker("/test.jpg"); Assert.IsTrue(result); } + + [TestMethod] + public void IsEnabled_FalseDueFeatureFlag() + { + var appSettings = new AppSettings { UseLocalDesktop = false }; + var service = new OpenEditorDesktopService(appSettings, + new FakeIOpenApplicationNativeService(new List(), "test"), + new FakeIOpenEditorPreflight(new List())); + var result = service.IsEnabled(); + Assert.IsFalse(result); + } + + [TestMethod] + public void IsEnabled_True() + { + var appSettings = new AppSettings + { + UseLocalDesktop = true // feature flag enabled + }; + var service = new OpenEditorDesktopService(appSettings, + // Default is supported in mock service + new FakeIOpenApplicationNativeService(new List(), "test"), + new FakeIOpenEditorPreflight(new List())); + var result = service.IsEnabled(); + Assert.IsTrue(result); + } + + [TestMethod] + public void IsEnabled_FalseDuePlatformNotSupported() + { + var appSettings = new AppSettings { UseLocalDesktop = true }; + var service = new OpenEditorDesktopService(appSettings, + // Is supported false! => + new FakeIOpenApplicationNativeService(new List(), "test", false), + new FakeIOpenEditorPreflight(new List())); + var result = service.IsEnabled(); + Assert.IsFalse(result); + } + + [TestMethod] + public void FilterListOpenDefaultEditorAndSpecificEditor_Test() + { + // Arrange + var inputList = new List + { + new PathImageFormatExistsAppPathModel + { + FullFilePath = "file1.txt", + Status = FileIndexItem.ExifStatus.Ok, + AppPath = string.Empty + }, + new PathImageFormatExistsAppPathModel + { + FullFilePath = "file2.txt", + Status = FileIndexItem.ExifStatus.Ok, + AppPath = "editor.exe" + }, + new PathImageFormatExistsAppPathModel + { + FullFilePath = "file3.txt", + Status = FileIndexItem.ExifStatus.OperationNotSupported, + AppPath = string.Empty + }, + new PathImageFormatExistsAppPathModel + { + FullFilePath = "file4.txt", + Status = FileIndexItem.ExifStatus.Ok, + AppPath = string.Empty + } + }; + + // Act + var result = + OpenEditorDesktopService.FilterListOpenDefaultEditorAndSpecificEditor(inputList); + + // Assert + Assert.AreEqual(2, result.Item1.Count); // Expected number of files without AppPath + Assert.IsTrue( + result.Item1 + .Contains("file1.txt")); // Make sure file1.txt is in the list without AppPath + Assert.IsFalse( + result.Item1 + .Contains("file2.txt")); // Make sure file2.txt is not in the list without AppPath + Assert.AreEqual(1, result.Item2.Count); // Expected number of files with AppPath + Assert.IsTrue(result.Item2.Exists(x => + x.FullFilePath == "file2.txt" && + x.AppPath == + "editor.exe")); // Make sure file2.txt is in the list with AppPath and has correct editor + } + + [TestMethod] + public void FilterListOpenSpecificEditor_Test() + { + // Arrange + var inputList = new List + { + new PathImageFormatExistsAppPathModel + { + FullFilePath = "file1.txt", + Status = FileIndexItem.ExifStatus.Ok, + AppPath = "" + }, + new PathImageFormatExistsAppPathModel + { + FullFilePath = "file2.txt", + Status = FileIndexItem.ExifStatus.Ok, + AppPath = "editor.exe" + }, + new PathImageFormatExistsAppPathModel + { + FullFilePath = "file3.txt", + Status = FileIndexItem.ExifStatus.NotFoundNotInIndex, + AppPath = "" + }, + new PathImageFormatExistsAppPathModel + { + FullFilePath = "file4.txt", + Status = FileIndexItem.ExifStatus.Ok, + AppPath = string.Empty + } + }; + + // Act + var result = + OpenEditorDesktopService.FilterListOpenDefaultEditorAndSpecificEditor(inputList); + + // Assert + Assert.AreEqual(1, result.Item2.Count); // Expected number of files with AppPath + Assert.IsTrue(result.Item2.Exists(x => + x is { FullFilePath: "file2.txt", AppPath: "editor.exe" })); + // Make sure file2.txt is in the list with AppPath and has correct editor + } } From 95a5ff42af3a570af09264e4d6161a6859bdf7e3 Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 22 Feb 2024 18:52:27 +0100 Subject: [PATCH 082/125] 0 issue --- .../modal-desktop-editor-open-selection-confirmation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-selection-confirmation/modal-desktop-editor-open-selection-confirmation.tsx b/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-selection-confirmation/modal-desktop-editor-open-selection-confirmation.tsx index ad1db10121..48e85dcdea 100644 --- a/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-selection-confirmation/modal-desktop-editor-open-selection-confirmation.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-selection-confirmation/modal-desktop-editor-open-selection-confirmation.tsx @@ -25,7 +25,7 @@ export async function OpenDesktop( messageDesktopEditorUnableToOpen: string ): Promise { const toDesktopOpenList = new URLPath().MergeSelectFileIndexItem(select, state.fileIndexItems); - if (!toDesktopOpenList) return false; + if (!toDesktopOpenList || toDesktopOpenList.length == 0) return false; const selectParams = new URLPath().ArrayToCommaSeparatedStringOneParent(toDesktopOpenList, ""); const urlOpen = new UrlQuery().UrlApiDesktopEditorOpen(); From 6d78d80fda0ca615db733586a7a99391746d480e Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 22 Feb 2024 18:56:29 +0100 Subject: [PATCH 083/125] fix test --- ...l-desktop-editor-open-selection-confirmation.spec.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-selection-confirmation/modal-desktop-editor-open-selection-confirmation.spec.tsx b/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-selection-confirmation/modal-desktop-editor-open-selection-confirmation.spec.tsx index 87e1129f34..f4fd3be162 100644 --- a/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-selection-confirmation/modal-desktop-editor-open-selection-confirmation.spec.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-selection-confirmation/modal-desktop-editor-open-selection-confirmation.spec.tsx @@ -88,11 +88,13 @@ describe("ModalDesktopEditorOpenConfirmation", () => { const setIsLoading = jest.fn(); const mockIConnectionDefaultResolve: Promise = Promise.resolve({ - data: false, + data: true, statusCode: 200 }); const mockFetchPost = jest .spyOn(FetchPost, "default") + .mockReset() + .mockImplementation(() => mockIConnectionDefaultResolve) .mockImplementation(() => mockIConnectionDefaultResolve); const component = render( @@ -100,7 +102,7 @@ describe("ModalDesktopEditorOpenConfirmation", () => { isOpen={true} handleExit={handleExit} state={exampleState} - select={[]} + select={["test.jpg"]} setIsLoading={setIsLoading} isCollections={false} /> @@ -126,6 +128,7 @@ describe("ModalDesktopEditorOpenConfirmation", () => { }); const mockFetchPost = jest .spyOn(FetchPost, "default") + .mockReset() .mockImplementation(() => mockIConnectionDefaultResolve); const component = render( @@ -133,7 +136,7 @@ describe("ModalDesktopEditorOpenConfirmation", () => { isOpen={true} handleExit={handleExit} state={exampleState} - select={[]} + select={["test.jpg"]} setIsLoading={setIsLoading} isCollections={false} /> From 319aa5d798291ac5320bd7cf714b2edc6b4630d3 Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 22 Feb 2024 19:27:04 +0100 Subject: [PATCH 084/125] add tests --- .../clientapp/src/shared/keyboard.spec.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/starsky/starsky/clientapp/src/shared/keyboard.spec.ts b/starsky/starsky/clientapp/src/shared/keyboard.spec.ts index 6716bf48fb..b253639b02 100644 --- a/starsky/starsky/clientapp/src/shared/keyboard.spec.ts +++ b/starsky/starsky/clientapp/src/shared/keyboard.spec.ts @@ -5,6 +5,14 @@ describe("keyboard", () => { describe("isInForm", () => { it("null input", () => { // new EventTarget() = not supported in Safari + + const result = keyboard.isInForm(undefined); + + expect(result).toBeNull(); + }); + + it("null input 2", () => { + // new EventTarget() = not supported in Safari const eventTarget: EventTarget = new EventTarget(); const event = new KeyboardEvent("keydown", { keyCode: 37, @@ -52,6 +60,47 @@ describe("keyboard", () => { }); describe("SetFocusOnEndField", () => { + it("no child items", () => { + const focusSpy = jest.fn(); + new Keyboard().SetFocusOnEndField({ + focus: focusSpy, + childNodes: [] + } as unknown as HTMLDivElement); + + expect(focusSpy).toHaveBeenCalledTimes(1); + }); + + it("text content", () => { + const focusSpy = jest.fn(); + new Keyboard().SetFocusOnEndField({ + focus: focusSpy, + childNodes: [{}] // text content is missing + } as unknown as HTMLDivElement); + + expect(focusSpy).toHaveBeenCalledTimes(0); + }); + + it("missing range", () => { + const focusSpy = jest.fn(); + const selectionSpy = jest.spyOn(window, "getSelection").mockImplementationOnce(() => null); + jest.spyOn(document, "createRange").mockImplementationOnce(() => { + return { + setStart: jest.fn(), + collapse: jest.fn() + } as unknown as Range; + }); + new Keyboard().SetFocusOnEndField({ + focus: focusSpy, + childNodes: [ + { + textContent: "hi" + } + ] + } as unknown as HTMLDivElement); + + expect(selectionSpy).toHaveBeenCalledTimes(1); + }); + it("input", () => { const target = document.createElement("div"); target.className = "test"; From ac43e8c949bb8356949fd61ab3597755005b3c66 Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 22 Feb 2024 19:45:04 +0100 Subject: [PATCH 085/125] add for single page --- ...ion-desktop-editor-open-selection.spec.tsx | 25 ++++- ...-desktop-editor-open-selection.stories.tsx | 36 +++++-- ...u-option-desktop-editor-open-selection.tsx | 11 +- ...ion-desktop-editor-open-single.stories.tsx | 30 ++++++ ...menu-option-desktop-editor-open-single.tsx | 101 ++++++++++++++++++ ...or-open-selection-confirmation.stories.tsx | 3 +- 6 files changed, 191 insertions(+), 15 deletions(-) create mode 100644 starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.stories.tsx create mode 100644 starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.tsx diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.spec.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.spec.tsx index df873a1702..ae4f5a98cc 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.spec.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.spec.tsx @@ -391,7 +391,21 @@ describe("ModalDesktopEditorOpenConfirmation", () => { state, jest.fn(), "", - jest.fn() + jest.fn(), + false + ); + expect(result).toBeFalsy(); + }); + + it("readonly", async () => { + const result = await StartMenuOptionDesktopEditorOpenSelection( + ["test"], + false, + state, + jest.fn(), + "", + jest.fn(), + true ); expect(result).toBeFalsy(); }); @@ -420,7 +434,8 @@ describe("ModalDesktopEditorOpenConfirmation", () => { state, setIsError, messageDesktopEditorUnableToOpen, - setModalConfirmationOpenFiles + setModalConfirmationOpenFiles, + false ); expect(setModalConfirmationOpenFiles).toHaveBeenCalledWith(true); expect(setIsError).not.toHaveBeenCalled(); // Error should not be set in this case @@ -449,7 +464,8 @@ describe("ModalDesktopEditorOpenConfirmation", () => { state, setIsError, messageDesktopEditorUnableToOpen, - setModalConfirmationOpenFiles + setModalConfirmationOpenFiles, + false ); expect(setModalConfirmationOpenFiles).not.toHaveBeenCalled(); // Modal confirmation should not be set in this case expect(setIsError).not.toHaveBeenCalled(); // Error should not be set in this case @@ -488,7 +504,8 @@ describe("ModalDesktopEditorOpenConfirmation", () => { state, setIsError, messageDesktopEditorUnableToOpen, - setModalConfirmationOpenFiles + setModalConfirmationOpenFiles, + false ); expect(setModalConfirmationOpenFiles).not.toHaveBeenCalled(); // Modal confirmation should not be set in this case expect(setIsError).toHaveBeenCalled(); diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.stories.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.stories.tsx index fd557bc90d..ae6c9b6e09 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.stories.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.stories.tsx @@ -1,8 +1,9 @@ +import { IArchiveProps } from "../../../interfaces/IArchiveProps"; import MoreMenu from "../../atoms/more-menu/more-menu"; import MenuOptionDesktopEditorOpenSelection from "./menu-option-desktop-editor-open-selection"; export default { - title: "components/molecules/menu-option-desktop-editor-open" + title: "components/molecules/menu-option-desktop-editor-open-selection" }; export const Default = () => { @@ -17,7 +18,7 @@ export const Default = () => { parentDirectory: "/" } ] - } as any + } as unknown as IArchiveProps } isReadOnly={false} select={["default.jpg"]} @@ -28,7 +29,7 @@ export const Default = () => { Default.storyName = "default (no dialog)"; -export const Case2 = () => { +export const WithDialog = () => { return ( {}}> { { fileIndexItems: [ { - fileName: "true.jpg", + fileName: "true.jpg", // in mock is set that true.jpg gives a dialog parentDirectory: "/" } ] - } as any + } as unknown as IArchiveProps } isReadOnly={false} select={["true.jpg"]} @@ -49,4 +50,27 @@ export const Case2 = () => { ); }; -Case2.storyName = "with dialog"; +WithDialog.storyName = "with dialog"; + +export const ReadOnly = () => { + return ( + {}}> + + + ); +}; + +ReadOnly.storyName = "ReadOnly"; diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.tsx index 4d07163ab6..de1fa748a1 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.tsx @@ -50,10 +50,11 @@ export async function StartMenuOptionDesktopEditorOpenSelection( state: IArchiveProps, setIsError: React.Dispatch>, messageDesktopEditorUnableToOpen: string, - setModalConfirmationOpenFiles: (value: React.SetStateAction) => void + setModalConfirmationOpenFiles: (value: React.SetStateAction) => void, + isReadOnly: boolean ) { const toDesktopOpenList = new URLPath().MergeSelectFileIndexItem(select, state.fileIndexItems); - if (!toDesktopOpenList || toDesktopOpenList.length === 0) return; + if (!toDesktopOpenList || toDesktopOpenList.length === 0 || isReadOnly) return; const selectParams = new URLPath().ArrayToCommaSeparatedStringOneParent(toDesktopOpenList, ""); const urlCheck = new UrlQuery().UrlApiDesktopEditorOpenAmountConfirmationChecker(); @@ -102,7 +103,8 @@ const MenuOptionDesktopEditorOpenSelection: React.FunctionComponent { // do nothing }); @@ -141,7 +143,8 @@ const MenuOptionDesktopEditorOpenSelection: React.FunctionComponent { + return ( + {}}> + + + ); +}; + +Default.storyName = "default (no dialog)"; + +export const ReadOnly = () => { + return ( + {}}> + + + ); +}; + +ReadOnly.storyName = "readonly"; diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.tsx new file mode 100644 index 0000000000..a3529e467a --- /dev/null +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.tsx @@ -0,0 +1,101 @@ +import React, { memo, useState } from "react"; +import useFetch from "../../../hooks/use-fetch"; +import useGlobalSettings from "../../../hooks/use-global-settings"; +import useHotKeys from "../../../hooks/use-keyboard/use-hotkeys"; +import { IEnvFeatures } from "../../../interfaces/IEnvFeatures"; +import localization from "../../../localization/localization.json"; +import FetchPost from "../../../shared/fetch/fetch-post"; +import { Language } from "../../../shared/language"; +import { UrlQuery } from "../../../shared/url-query"; +import MenuOption from "../../atoms/menu-option/menu-option"; +import Notification, { NotificationType } from "../../atoms/notification/notification"; + +interface IMenuOptionDesktopEditorOpenSingleProps { + subPath: string; + collections: boolean; + isReadOnly: boolean; + setEnableMoreMenu?: React.Dispatch>; +} + +export async function OpenDesktopSingle( + subPath: string, + collections: boolean, + setIsError: React.Dispatch>, + messageDesktopEditorUnableToOpen: string, + isReadOnly: boolean +) { + if (isReadOnly) { + return; + } + const urlOpen = new UrlQuery().UrlApiDesktopEditorOpen(); + + const bodyParams = new URLSearchParams(); + bodyParams.append("f", subPath); + bodyParams.append("collections", collections.toString()); + + const openDesktopResult = await FetchPost(urlOpen, bodyParams.toString()); + if (openDesktopResult.statusCode >= 300) { + setIsError(messageDesktopEditorUnableToOpen); + } +} + +const MenuOptionDesktopEditorOpenSingle: React.FunctionComponent = + memo(({ subPath, collections, isReadOnly }) => { + // Check API to know if feature is needed! + const featuresResult = useFetch(new UrlQuery().UrlApiFeaturesAppSettings(), "get"); + const dataFeatures = featuresResult?.data as IEnvFeatures | undefined; + + // Get language keys + const settings = useGlobalSettings(); + const language = new Language(settings.language); + const MessageDesktopEditorUnableToOpen = language.key( + localization.MessageDesktopEditorUnableToOpen + ); + + // for showing a notification + const [isError, setIsError] = useState(""); + + /** + * Open editor with keys + */ + useHotKeys({ key: "e", ctrlKeyOrMetaKey: true }, () => { + OpenDesktopSingle( + subPath, + collections, + setIsError, + MessageDesktopEditorUnableToOpen, + isReadOnly + ).then(() => { + // do nothing + }); + }); + + return ( + <> + {isError !== "" ? ( + setIsError("")} type={NotificationType.danger}> + {isError} + + ) : null} + + {dataFeatures?.openEditorEnabled === true ? ( + + OpenDesktopSingle( + subPath, + collections, + setIsError, + MessageDesktopEditorUnableToOpen, + isReadOnly + ) + } + localization={localization.MessageDesktopEditorOpenSingleFile} + /> + ) : null} + + ); + }); + +export default MenuOptionDesktopEditorOpenSingle; diff --git a/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-selection-confirmation/modal-desktop-editor-open-selection-confirmation.stories.tsx b/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-selection-confirmation/modal-desktop-editor-open-selection-confirmation.stories.tsx index 3d8aa1e843..d902a1aa0a 100644 --- a/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-selection-confirmation/modal-desktop-editor-open-selection-confirmation.stories.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/modal-desktop-editor-open-selection-confirmation/modal-desktop-editor-open-selection-confirmation.stories.tsx @@ -1,3 +1,4 @@ +import { IArchiveProps } from "../../../interfaces/IArchiveProps"; import ModalDesktopEditorOpenSelectionConfirmation from "./modal-desktop-editor-open-selection-confirmation"; export default { @@ -15,7 +16,7 @@ export const Default = () => { parentDirectory: "/" } ] - } as any + } as unknown as IArchiveProps } isCollections={true} select={[]} From a8cc3ffdb29e7399b92a7ca21455685dc771eff4 Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 22 Feb 2024 19:48:00 +0100 Subject: [PATCH 086/125] fix readonly --- ...ption-desktop-editor-open-selection.spec.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.spec.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.spec.tsx index ae4f5a98cc..050c4b9a6f 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.spec.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.spec.tsx @@ -72,7 +72,7 @@ describe("ModalDesktopEditorOpenConfirmation", () => { const component = render( {}} @@ -80,16 +80,15 @@ describe("ModalDesktopEditorOpenConfirmation", () => { ); fireEvent.keyDown(document.body, { key: "e", ctrlKey: true }); - expect(fetchPostSpy).toHaveBeenCalled(); - expect(fetchPostSpy).toHaveBeenCalledTimes(1); - expect(fetchPostSpy).toHaveBeenNthCalledWith( - 1, - new UrlQuery().UrlApiDesktopEditorOpenAmountConfirmationChecker(), - "f=%2Ffile1.jpg%3B%2Ffile2.jpg" - ); - await waitFor(() => { + expect(fetchPostSpy).toHaveBeenCalled(); expect(fetchPostSpy).toHaveBeenCalledTimes(2); + expect(fetchPostSpy).toHaveBeenNthCalledWith( + 1, + new UrlQuery().UrlApiDesktopEditorOpenAmountConfirmationChecker(), + "f=%2Ffile1.jpg%3B%2Ffile2.jpg" + ); + expect(fetchPostSpy).toHaveBeenNthCalledWith( 2, new UrlQuery().UrlApiDesktopEditorOpen(), From 70a427edee7a4910710d4dfc1648b9854f339d59 Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 22 Feb 2024 19:57:21 +0100 Subject: [PATCH 087/125] add to menu --- .../organisms/menu-detail-view/menu-detail-view.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/starsky/starsky/clientapp/src/components/organisms/menu-detail-view/menu-detail-view.tsx b/starsky/starsky/clientapp/src/components/organisms/menu-detail-view/menu-detail-view.tsx index 18292894ae..be0005d621 100644 --- a/starsky/starsky/clientapp/src/components/organisms/menu-detail-view/menu-detail-view.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/menu-detail-view/menu-detail-view.tsx @@ -23,12 +23,13 @@ import MenuOption from "../../atoms/menu-option/menu-option"; import MoreMenu from "../../atoms/more-menu/more-menu"; import Preloader from "../../atoms/preloader/preloader"; import IsSearchQueryMenuSearchItem from "../../molecules/is-search-query-menu-search-item/is-search-query-menu-search-item"; +import MenuOptionDesktopEditorOpenSingle from "../../molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.tsx"; +import MenuOptionRotateImage90 from "../../molecules/menu-option-rotate-image-90/menu-option-rotate-image-90.tsx"; import ModalDetailviewRenameFile from "../modal-detailview-rename-file/modal-detailview-rename-file"; import ModalDownload from "../modal-download/modal-download"; import ModalMoveFile from "../modal-move-file/modal-move-file"; import ModalPublishToggleWrapper from "../modal-publish/modal-publish-toggle-wrapper"; import { GoToParentFolder } from "./internal/go-to-parent-folder"; -import MenuOptionRotateImage90 from "../../molecules/menu-option-rotate-image-90/menu-option-rotate-image-90.tsx"; interface MenuDetailViewProps { state: IDetailView; @@ -359,6 +360,12 @@ const MenuDetailView: React.FunctionComponent = ({ state, d set={setIsModalPublishOpen} localization={localization.MessagePublish} /> + + From 040eed2b29d92d15360381b4a934d558d0374f46 Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 22 Feb 2024 20:15:02 +0100 Subject: [PATCH 088/125] rename --- .../menu-option-desktop-editor-open-single.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.tsx index a3529e467a..320ee8f63d 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.tsx @@ -81,7 +81,7 @@ const MenuOptionDesktopEditorOpenSingle: React.FunctionComponent OpenDesktopSingle( subPath, From e24c49e7d2b36f447d6eb942a8f023255d622e02 Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 22 Feb 2024 20:31:27 +0100 Subject: [PATCH 089/125] add test --- ...option-desktop-editor-open-single.spec.tsx | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.spec.tsx diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.spec.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.spec.tsx new file mode 100644 index 0000000000..84c2466963 --- /dev/null +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.spec.tsx @@ -0,0 +1,151 @@ +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import * as useFetch from "../../../hooks/use-fetch"; +import { IConnectionDefault } from "../../../interfaces/IConnectionDefault"; +import { IEnvFeatures } from "../../../interfaces/IEnvFeatures"; +import * as FetchPost from "../../../shared/fetch/fetch-post"; +import { UrlQuery } from "../../../shared/url-query"; +import * as Notification from "../../atoms/notification/notification"; +import MenuOptionDesktopEditorOpenSingle from "./menu-option-desktop-editor-open-single"; + +describe("MenuOptionDesktopEditorOpenSingle", () => { + it("should render without errors", () => { + render(); + // You can add more specific assertions about the rendered output if needed + }); + + it("should call OpenDesktopSingle when MenuOption is clicked", () => { + const mockGetIConnectionDefaultFeatureToggle = { + statusCode: 200, + data: { + openEditorEnabled: true + } as IEnvFeatures + } as IConnectionDefault; + + const useFetchSpy = jest + .spyOn(useFetch, "default") + .mockImplementationOnce(() => mockGetIConnectionDefaultFeatureToggle) + .mockImplementationOnce(() => mockGetIConnectionDefaultFeatureToggle); + + const mockIConnectionDefaultResolve: Promise = Promise.resolve({ + data: true, + statusCode: 200 + } as IConnectionDefault); + + const fetchPostSpy = jest + .spyOn(FetchPost, "default") + .mockReset() + .mockImplementationOnce(() => mockIConnectionDefaultResolve); + + const subPath = "/test.jpg"; + const collections = true; + const isReadOnly = false; + + const container = render( + + ); + + expect(useFetchSpy).toHaveBeenCalled(); + + fireEvent.click(screen.getByTestId("menu-option-desktop-editor-open-single")); + + expect(fetchPostSpy).toHaveBeenCalled(); + expect(fetchPostSpy).toHaveBeenNthCalledWith( + 1, + new UrlQuery().UrlApiDesktopEditorOpen(), + "f=%2Ftest.jpg&collections=true" + ); + + container.unmount(); + }); + + it("feature toggle disabled", () => { + const mockGetIConnectionDefaultFeatureToggle = { + statusCode: 200, + data: { + openEditorEnabled: false + } as IEnvFeatures + } as IConnectionDefault; + + const useFetchSpy = jest + .spyOn(useFetch, "default") + .mockImplementationOnce(() => mockGetIConnectionDefaultFeatureToggle); + + const subPath = "/test.jpg"; + const collections = true; + const isReadOnly = false; + + const container = render( + + ); + + waitFor(() => { + expect(useFetchSpy).toHaveBeenCalled(); + + expect(screen.queryByTestId("menu-option-desktop-editor-open-single")).toBeFalsy(); + }); + + container.unmount(); + }); + + it("should hide feature toggle - set Error", async () => { + const mockGetIConnectionDefaultFeatureToggle = { + statusCode: 200, + data: { + openEditorEnabled: true + } as IEnvFeatures + } as IConnectionDefault; + + const useFetchSpy = jest + .spyOn(useFetch, "default") + .mockReset() + .mockImplementationOnce(() => mockGetIConnectionDefaultFeatureToggle); + + const notificationSpy = jest.spyOn(Notification, "default").mockImplementationOnce(() => <>); + + const mockIConnectionDefaultResolve: Promise = Promise.resolve({ + data: null, + statusCode: 500 + } as IConnectionDefault); + + const fetchPostSpy = jest + .spyOn(FetchPost, "default") + .mockReset() + .mockImplementationOnce(() => mockIConnectionDefaultResolve); + + const subPath = "/test.jpg"; + const collections = true; + const isReadOnly = false; + + const container = render( + + ); + + expect(useFetchSpy).toHaveBeenCalled(); + + fireEvent.click(screen.getByTestId("menu-option-desktop-editor-open-single")); + + expect(fetchPostSpy).toHaveBeenCalled(); + expect(fetchPostSpy).toHaveBeenNthCalledWith( + 1, + new UrlQuery().UrlApiDesktopEditorOpen(), + "f=%2Ftest.jpg&collections=true" + ); + + await waitFor(() => { + expect(notificationSpy).toHaveBeenCalled(); + }); + container.unmount(); + }); +}); From 3bc8c9f2e523794fd2f3094bf92cbb7ab50d6ef9 Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 22 Feb 2024 20:39:34 +0100 Subject: [PATCH 090/125] add tests --- ...option-desktop-editor-open-single.spec.tsx | 44 ++++++++++++++++++- ...menu-option-desktop-editor-open-single.tsx | 5 ++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.spec.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.spec.tsx index 84c2466963..c1479096e9 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.spec.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.spec.tsx @@ -5,7 +5,9 @@ import { IEnvFeatures } from "../../../interfaces/IEnvFeatures"; import * as FetchPost from "../../../shared/fetch/fetch-post"; import { UrlQuery } from "../../../shared/url-query"; import * as Notification from "../../atoms/notification/notification"; -import MenuOptionDesktopEditorOpenSingle from "./menu-option-desktop-editor-open-single"; +import MenuOptionDesktopEditorOpenSingle, { + OpenDesktopSingle +} from "./menu-option-desktop-editor-open-single"; describe("MenuOptionDesktopEditorOpenSingle", () => { it("should render without errors", () => { @@ -62,6 +64,41 @@ describe("MenuOptionDesktopEditorOpenSingle", () => { container.unmount(); }); + it("calls StartMenuOptionDesktopEditorOpenSelection on hotkey trigger", async () => { + const mockIConnectionDefaultResolve: Promise = Promise.resolve({ + data: true, + statusCode: 200 + } as IConnectionDefault); + + const fetchPostSpy = jest + .spyOn(FetchPost, "default") + .mockReset() + .mockImplementationOnce(() => mockIConnectionDefaultResolve) + .mockImplementationOnce(() => mockIConnectionDefaultResolve); + + const container = render( + + ); + + fireEvent.keyDown(document.body, { key: "e", ctrlKey: true }); + + await waitFor(() => { + expect(fetchPostSpy).toHaveBeenCalled(); + expect(fetchPostSpy).toHaveBeenCalledTimes(1); + + expect(fetchPostSpy).toHaveBeenNthCalledWith( + 1, + new UrlQuery().UrlApiDesktopEditorOpen(), + "f=%2Ftest.jpg&collections=false" + ); + container.unmount(); + }); + }); + it("feature toggle disabled", () => { const mockGetIConnectionDefaultFeatureToggle = { statusCode: 200, @@ -148,4 +185,9 @@ describe("MenuOptionDesktopEditorOpenSingle", () => { }); container.unmount(); }); + + it("OpenDesktopSingle readonly should skip", async () => { + const result = await OpenDesktopSingle("/", false, jest.fn(), "error", true); + expect(result).toBeFalsy(); + }); }); diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.tsx index 320ee8f63d..4ea7288709 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.tsx @@ -23,9 +23,9 @@ export async function OpenDesktopSingle( setIsError: React.Dispatch>, messageDesktopEditorUnableToOpen: string, isReadOnly: boolean -) { +): Promise { if (isReadOnly) { - return; + return false; } const urlOpen = new UrlQuery().UrlApiDesktopEditorOpen(); @@ -37,6 +37,7 @@ export async function OpenDesktopSingle( if (openDesktopResult.statusCode >= 300) { setIsError(messageDesktopEditorUnableToOpen); } + return true; } const MenuOptionDesktopEditorOpenSingle: React.FunctionComponent = From 4b9f6123cf512212bc5c614b9f8e5c4c8fb7b01a Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 22 Feb 2024 22:26:51 +0100 Subject: [PATCH 091/125] add WIP --- .../ContentSecurityPolicyMiddleware.cs | 2 +- starskydesktop/.vscode/launch.bak.json | 20 ----------- starskydesktop/package.json | 2 +- .../src/app/main-window/create-main-window.ts | 23 +++++++------ .../app/main-window/on-headers-received.ts | 33 ------------------- starskydesktop/src/app/menu/app-menu.ts | 29 ++++++++++++++-- starskydesktop/src/main/main.ts | 22 ++++++++++++- 7 files changed, 62 insertions(+), 69 deletions(-) delete mode 100644 starskydesktop/.vscode/launch.bak.json delete mode 100644 starskydesktop/src/app/main-window/on-headers-received.ts diff --git a/starsky/starsky.foundation.platform/Middleware/ContentSecurityPolicyMiddleware.cs b/starsky/starsky.foundation.platform/Middleware/ContentSecurityPolicyMiddleware.cs index eb8ddfa2ce..a3a2f669e7 100644 --- a/starsky/starsky.foundation.platform/Middleware/ContentSecurityPolicyMiddleware.cs +++ b/starsky/starsky.foundation.platform/Middleware/ContentSecurityPolicyMiddleware.cs @@ -38,7 +38,7 @@ public async Task Invoke(HttpContext httpContext) "default-src 'none'; img-src 'self' https://a.tile.openstreetmap.org/ " + "https://b.tile.openstreetmap.org/ " + "https://c.tile.openstreetmap.org/; script-src 'self'; " + - $"connect-src 'self' {socketUrl} {socketUrlWithPort};" + + $"connect-src 'self' {socketUrl} {socketUrlWithPort} starsky://*;" + "style-src 'self'; " + "font-src 'self'; " + "frame-ancestors 'none'; " + diff --git a/starskydesktop/.vscode/launch.bak.json b/starskydesktop/.vscode/launch.bak.json deleted file mode 100644 index c8df8623f5..0000000000 --- a/starskydesktop/.vscode/launch.bak.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "name": "vscode-jest-tests", - "request": "launch", - "args": ["test", "--runInBand"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen", - "disableOptimisticBPs": true, - "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/react-scripts", - "protocol": "inspector" - } - ] -} diff --git a/starskydesktop/package.json b/starskydesktop/package.json index 457a57a771..b0863899e2 100644 --- a/starskydesktop/package.json +++ b/starskydesktop/package.json @@ -24,7 +24,7 @@ "dist": "npm run prod && electron-builder build --publish never", "build:win": "electron-builder build --win", "build:mac": "electron-builder build --mac", - "build:runtime": "cd ../starsky && pwsh build.ps1 -runtime osx-arm64,osx-x64,win-x64 -ready-to-run", + "build:runtime": "cd ../starsky && pwsh build.ps1 -runtime osx-arm64,osx-x64,win-x64 -ready-to-run -no-tests", "update": "npx --yes npm-check-updates", "update:install": "npx --yes npm-check-updates -u && npm install", "update:yes": "npm run update:install" diff --git a/starskydesktop/src/app/main-window/create-main-window.ts b/starskydesktop/src/app/main-window/create-main-window.ts index fb25933ff0..5cd81cbcaf 100644 --- a/starskydesktop/src/app/main-window/create-main-window.ts +++ b/starskydesktop/src/app/main-window/create-main-window.ts @@ -8,15 +8,12 @@ import { onHeaderReceived } from "./on-headers-received"; import { removeRememberUrl, saveRememberUrl } from "./save-remember-url"; import { spellCheck } from "./spellcheck"; -async function createMainWindow( - openSpecificUrl: string, - offset = 0, -): Promise { +async function createMainWindow(openSpecificUrl: string, offset = 0): Promise { const mainWindowStateKeeper = await windowStateKeeper("main"); const { x, y } = getNewFocusedWindow( mainWindowStateKeeper.x - offset, - mainWindowStateKeeper.y - offset, + mainWindowStateKeeper.y - offset ); let newWindow = new BrowserWindow({ @@ -40,12 +37,18 @@ async function createMainWindow( // Add Starsky as user agent also in develop mode newWindow.webContents.userAgent = `${newWindow.webContents.userAgent} starsky/${GetAppVersion()}`; + // newWindow.webContents + // .executeJavaScript("alert('hi')", false) + // .then((result) => { + // logger.info(result); + // }) + // .catch((result) => { + // logger.info(result); + // }); + mainWindowStateKeeper.track(newWindow); - const location = path.join( - __dirname, - "client/pages/redirect/reload-redirect.html", - ); + const location = path.join(__dirname, "client/pages/redirect/reload-redirect.html"); await newWindow.loadFile(location, { query: { "remember-url": openSpecificUrl }, @@ -62,7 +65,7 @@ async function createMainWindow( console.log(url); return { - action: 'allow', + action: "allow", overrideBrowserWindowOptions: { webPreferences: { devTools: true, // allow diff --git a/starskydesktop/src/app/main-window/on-headers-received.ts b/starskydesktop/src/app/main-window/on-headers-received.ts deleted file mode 100644 index a24af7adcb..0000000000 --- a/starskydesktop/src/app/main-window/on-headers-received.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { BrowserWindow } from "electron"; - -export function onHeaderReceived(newWindow: BrowserWindow) { - newWindow.webContents.session.webRequest.onHeadersReceived( - (res, callback) => { - // @TODO: re-enable - - // var currentSettings = appConfig.get("remote_settings_" + isPackaged()); - // var localhost = "http://localhost:9609 "; // with space on end - // ${appPort} - - // let whitelistDomain = localhost; - // if (currentSettings && currentSettings.location) { - // whitelistDomain = !currentSettings.location ? localhost : localhost + new URL(currentSettings.location).origin; - // } - - /// default-src 'none'; img-src 'self' https://a.tile.openstreetmap.org/ https://b.tile.openstreetmap.org/ https://c.tile.openstreetmap.org/; script-src 'self'; connect-src 'self' wss://starsky.server ;style-src 'self'; font-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; object-src 'none'; media-src 'self'; frame-src 'none'; manifest-src 'self'; block-all-mixed-content; - - // // When change also check if CSPMiddleware needs to be updated - // var csp = "default-src 'none'; img-src 'self' file://* https://www.openstreetmap.org https://tile.openstreetmap.org https://*.tile.openstreetmap.org " - // + whitelistDomain + "; " + "style-src file://* unsafe-inline https://www.openstreetmap.org " + whitelistDomain - // + "; script-src 'self' https://js.monitor.azure.com/scripts/b/ai.2.min.js file://* https://az416426.vo.msecnd.net; " + - // "connect-src 'self' https://dc.services.visualstudio.com/v2/track https://*.in.applicationinsights.azure.com//v2/track " + whitelistDomain + "; " + - // "font-src file://* " + whitelistDomain + "; media-src " + whitelistDomain + ";"; - - // if (!res.url.startsWith('devtools://') && !res.url.startsWith('http://localhost:3000/') ) { - // res.responseHeaders["Content-Security-Policy"] = csp; - // } - - callback({ cancel: false, responseHeaders: res.responseHeaders }); - } - ); -} diff --git a/starskydesktop/src/app/menu/app-menu.ts b/starskydesktop/src/app/menu/app-menu.ts index eca298102c..cf25562f3a 100644 --- a/starskydesktop/src/app/menu/app-menu.ts +++ b/starskydesktop/src/app/menu/app-menu.ts @@ -2,11 +2,27 @@ import { app, BrowserWindow, Menu, shell } from "electron"; -import { EditFile } from "../edit-file/edit-file"; import { IsDutch } from "../i18n/i18n"; import createMainWindow from "../main-window/create-main-window"; import { createSettingsWindow } from "../settings-window/create-settings-window"; +function sendKeybinding(win: BrowserWindow, keyCode: string, cmdOrCtrl: boolean) { + const modifiers = []; + + if (cmdOrCtrl) { + const isMac = process.platform === "darwin"; + if (isMac) { + modifiers.push("meta"); + } else { + modifiers.push("ctrl"); + } + } + + win.webContents.sendInputEvent({ type: "keyDown", modifiers, keyCode }); + win.webContents.sendInputEvent({ type: "char", modifiers, keyCode }); + win.webContents.sendInputEvent({ type: "keyUp", modifiers, keyCode }); +} + function AppMenu() { const isMac = process.platform === "darwin"; @@ -59,8 +75,15 @@ function AppMenu() { label: IsDutch() ? "Bewerk bestand in editor" : "Edit file in Editor", click: () => { const focusWindow = BrowserWindow.getFocusedWindow(); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - if (focusWindow) EditFile(focusWindow); + + sendKeybinding(focusWindow, "e", true); + + // focusWindow.webContents.sendInputEvent({ + // keyCode: "CommandOrControl+e", + // type: "keyDown", + // }); + // // eslint-disable-next-line @typescript-eslint/no-floating-promises + // if (focusWindow) EditFile(focusWindow); }, accelerator: "CmdOrCtrl+E", }, diff --git a/starskydesktop/src/main/main.ts b/starskydesktop/src/main/main.ts index ef757d9af7..3f9dc704f8 100755 --- a/starskydesktop/src/main/main.ts +++ b/starskydesktop/src/main/main.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ -import { app, BrowserWindow } from "electron"; +import { app, BrowserWindow, protocol } from "electron"; import * as os from "os"; +import logger from "src/app/logger/logger"; import { setupChildProcess } from "../app/child-process/setup-child-process"; import { MakeLogsPath } from "../app/config/logs-path"; import { MakeTempPath } from "../app/config/temp-path"; @@ -22,6 +23,18 @@ setupChildProcess(); MakeTempPath(); SetupFileWatcher(); +protocol.registerSchemesAsPrivileged([ + { + scheme: "app", + privileges: { + supportFetchAPI: true, + bypassCSP: true, + standard: true, + secure: true, + }, + }, +]); + console.log(`running in: :${os.arch()}`); // This method will be called when Electron has finished @@ -31,6 +44,13 @@ app.on("ready", () => { AppMenu(); DockMenu(); + protocol.handle("app", (req) => { + logger.info(req.url); + return new Response("

hello, world

", { + headers: { "content-type": "text/html" }, + }); + }); + IsRemote().then(async (isRemote) => { const splashWindows = await SetupSplash(); RestoreWarmupMainWindowAndCloseSplash(splashWindows, isRemote); From 444e94053e82aa19a6eaf93cba89c02334a3a3e4 Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 22 Feb 2024 22:31:41 +0100 Subject: [PATCH 092/125] remove unused --- .../ContentSecurityPolicyMiddleware.cs | 18 +-- .../internal/inline-search-suggest.tsx | 1 + .../menu-archive/menu-archive.spec.tsx | 7 +- .../menu-search/menu-search.spec.tsx | 12 +- .../src/router-app/router-app.spec.tsx | 3 +- .../clientapp/src/router-app/router-app.tsx | 40 +----- .../src/router-app/routes-config.tsx | 33 +++++ .../global-shortcuts.spec.tsx | 24 ++++ .../global-shortcuts/global-shortcuts.tsx | 16 +++ starskydesktop/src/app/edit-file/edit-file.ts | 14 +- starskydesktop/src/app/edit-file/open-path.ts | 68 +-------- .../src/app/ipc-bridge/ipc-bridge.spec.ts | 132 ++---------------- .../src/app/ipc-bridge/ipc-bridge.ts | 84 +++-------- .../main-window/create-main-window.spec.ts | 43 +++--- .../src/app/main-window/create-main-window.ts | 11 -- starskydesktop/src/app/menu/app-menu.ts | 109 ++++++++------- .../src/client/pages/settings/settings.html | 12 -- starskydesktop/src/main/main.ts | 22 +-- 18 files changed, 232 insertions(+), 417 deletions(-) create mode 100644 starsky/starsky/clientapp/src/router-app/routes-config.tsx create mode 100644 starsky/starsky/clientapp/src/shared/global-shortcuts/global-shortcuts.spec.tsx create mode 100644 starsky/starsky/clientapp/src/shared/global-shortcuts/global-shortcuts.tsx diff --git a/starsky/starsky.foundation.platform/Middleware/ContentSecurityPolicyMiddleware.cs b/starsky/starsky.foundation.platform/Middleware/ContentSecurityPolicyMiddleware.cs index a3a2f669e7..552e552fc3 100644 --- a/starsky/starsky.foundation.platform/Middleware/ContentSecurityPolicyMiddleware.cs +++ b/starsky/starsky.foundation.platform/Middleware/ContentSecurityPolicyMiddleware.cs @@ -38,7 +38,7 @@ public async Task Invoke(HttpContext httpContext) "default-src 'none'; img-src 'self' https://a.tile.openstreetmap.org/ " + "https://b.tile.openstreetmap.org/ " + "https://c.tile.openstreetmap.org/; script-src 'self'; " + - $"connect-src 'self' {socketUrl} {socketUrlWithPort} starsky://*;" + + $"connect-src 'self' {socketUrl} {socketUrlWithPort};" + "style-src 'self'; " + "font-src 'self'; " + "frame-ancestors 'none'; " + @@ -52,7 +52,7 @@ public async Task Invoke(HttpContext httpContext) // Currently not supported in Firefox and Safari (Edge user agent also includes the word Chrome) if ( httpContext.Request.Headers.UserAgent.Contains("Chrome") || - httpContext.Request.Headers.UserAgent.Contains("csp-evaluator") ) + httpContext.Request.Headers.UserAgent.Contains("csp-evaluator") ) { cspHeader += "require-trusted-types-for 'script'; "; } @@ -64,16 +64,16 @@ public async Task Invoke(HttpContext httpContext) // @see: https://www.permissionspolicy.com/ if ( string.IsNullOrEmpty( - httpContext.Response.Headers["Permissions-Policy"]) ) + httpContext.Response.Headers["Permissions-Policy"]) ) { httpContext.Response.Headers .Append("Permissions-Policy", "autoplay=(self), " + - "fullscreen=(self), " + - "geolocation=(self), " + - "picture-in-picture=(self), " + - "clipboard-read=(self), " + - "clipboard-write=(self), " + - "window-placement=(self)"); + "fullscreen=(self), " + + "geolocation=(self), " + + "picture-in-picture=(self), " + + "clipboard-read=(self), " + + "clipboard-write=(self), " + + "window-placement=(self)"); } if ( string.IsNullOrEmpty(httpContext.Response.Headers["Referrer-Policy"]) ) diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-inline-search/internal/inline-search-suggest.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-inline-search/internal/inline-search-suggest.tsx index 830d2b549b..bc46cf9607 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-inline-search/internal/inline-search-suggest.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-inline-search/internal/inline-search-suggest.tsx @@ -63,6 +63,7 @@ const InlineSearchSuggest: React.FunctionComponent = name: language.key(localization.MessagePreferences), url: new UrlQuery().UrlPreferencesPage(), key: "preferences" + // command + shift + k -> see GlobalShortcuts }, { name: language.key(localization.MessageLogout), diff --git a/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.spec.tsx b/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.spec.tsx index 04a12451ae..cf5f2aff44 100644 --- a/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.spec.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.spec.tsx @@ -1223,7 +1223,12 @@ describe("MenuArchive", () => { const component = render(); expect(useHotkeysSpy).toHaveBeenCalled(); - expect(useHotkeysSpy).toHaveBeenCalledTimes(1); + expect(useHotkeysSpy).toHaveBeenNthCalledWith( + 1, + { ctrlKeyOrMetaKey: true, key: "a" }, + expect.anything(), + [] + ); component.unmount(); }); diff --git a/starsky/starsky/clientapp/src/components/organisms/menu-search/menu-search.spec.tsx b/starsky/starsky/clientapp/src/components/organisms/menu-search/menu-search.spec.tsx index 620d755efa..142389c781 100644 --- a/starsky/starsky/clientapp/src/components/organisms/menu-search/menu-search.spec.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/menu-search/menu-search.spec.tsx @@ -3,6 +3,7 @@ import React from "react"; import { act } from "react-dom/test-utils"; import * as useHotKeys from "../../../hooks/use-keyboard/use-hotkeys"; import { IArchive } from "../../../interfaces/IArchive"; +import { IArchiveProps } from "../../../interfaces/IArchiveProps"; import { IExifStatus } from "../../../interfaces/IExifStatus"; import { Router } from "../../../router-app/router-app"; import * as MenuSearchBar from "../../molecules/menu-inline-search/menu-inline-search"; @@ -80,10 +81,17 @@ describe("MenuSearch", () => { .mockImplementationOnce(() => contextValues) .mockImplementationOnce(() => contextValues); - const component = render(); + const component = render( + + ); expect(useHotkeysSpy).toHaveBeenCalled(); - expect(useHotkeysSpy).toHaveBeenCalledTimes(1); + expect(useHotkeysSpy).toHaveBeenNthCalledWith( + 1, + { ctrlKeyOrMetaKey: true, key: "a" }, + expect.anything(), + [] + ); jest.spyOn(React, "useContext").mockRestore(); component.unmount(); diff --git a/starsky/starsky/clientapp/src/router-app/router-app.spec.tsx b/starsky/starsky/clientapp/src/router-app/router-app.spec.tsx index 93b7b12f33..6a88f8656d 100644 --- a/starsky/starsky/clientapp/src/router-app/router-app.spec.tsx +++ b/starsky/starsky/clientapp/src/router-app/router-app.spec.tsx @@ -8,7 +8,8 @@ import { ISearchList } from "../hooks/use-searchlist"; import * as NotFoundPage from "../pages/not-found-page"; import * as SearchPage from "../pages/search-page"; import * as TrashPage from "../pages/trash-page"; -import RouterApp, { Router, RoutesConfig } from "./router-app"; +import RouterApp, { Router } from "./router-app"; +import { RoutesConfig } from "./routes-config"; describe("Router", () => { it("default", () => { diff --git a/starsky/starsky/clientapp/src/router-app/router-app.tsx b/starsky/starsky/clientapp/src/router-app/router-app.tsx index 39ee0dd1a8..88bdc5725d 100644 --- a/starsky/starsky/clientapp/src/router-app/router-app.tsx +++ b/starsky/starsky/clientapp/src/router-app/router-app.tsx @@ -1,38 +1,12 @@ -import { RouteObject, RouterProvider, createBrowserRouter } from "react-router-dom"; -import { AccountRegisterPage } from "../pages/account-register-page"; -import { ContentPage } from "../pages/content-page"; -import ImportPage from "../pages/import-page"; -import { LoginPage } from "../pages/login-page"; -import { NotFoundPage } from "../pages/not-found-page"; -import { PreferencesPage } from "../pages/preferences-page"; -import { SearchPage } from "../pages/search-page"; -import { TrashPage } from "../pages/trash-page"; +import { RouterProvider, createBrowserRouter } from "react-router-dom"; +import { GlobalShortcuts } from "../shared/global-shortcuts/global-shortcuts"; +import { RoutesConfig } from "./routes-config"; -export const RoutesConfig: RouteObject[] = [ - { - path: "/", - element: , - errorElement: - }, - { path: "starsky/", element: }, - { path: "search", element: }, - { path: "starsky/search", element: }, - { path: "trash", element: }, - { path: "starsky/trash", element: }, - { path: "import", element: }, - { path: "starsky/import", element: }, - { path: "login", element: }, - { path: "starsky/login", element: }, - { path: "account/login", element: }, - { path: "starsky/account/login", element: }, - { path: "account/register", element: }, - { path: "starsky/account/register", element: }, - { path: "preferences", element: }, - { path: "starsky/preferences", element: }, - { path: "*", element: } -]; export const Router = createBrowserRouter(RoutesConfig); -const RouterApp = () => ; +const RouterApp = () => { + GlobalShortcuts(); + return ; +}; export default RouterApp; diff --git a/starsky/starsky/clientapp/src/router-app/routes-config.tsx b/starsky/starsky/clientapp/src/router-app/routes-config.tsx new file mode 100644 index 0000000000..8bd0af781c --- /dev/null +++ b/starsky/starsky/clientapp/src/router-app/routes-config.tsx @@ -0,0 +1,33 @@ +import { RouteObject } from "react-router-dom"; +import { AccountRegisterPage } from "../pages/account-register-page"; +import { ContentPage } from "../pages/content-page"; +import ImportPage from "../pages/import-page"; +import { LoginPage } from "../pages/login-page"; +import { NotFoundPage } from "../pages/not-found-page"; +import { PreferencesPage } from "../pages/preferences-page"; +import { SearchPage } from "../pages/search-page"; +import { TrashPage } from "../pages/trash-page"; + +export const RoutesConfig: RouteObject[] = [ + { + path: "/", + element: , + errorElement: + }, + { path: "starsky/", element: }, + { path: "search", element: }, + { path: "starsky/search", element: }, + { path: "trash", element: }, + { path: "starsky/trash", element: }, + { path: "import", element: }, + { path: "starsky/import", element: }, + { path: "login", element: }, + { path: "starsky/login", element: }, + { path: "account/login", element: }, + { path: "starsky/account/login", element: }, + { path: "account/register", element: }, + { path: "starsky/account/register", element: }, + { path: "preferences", element: }, + { path: "starsky/preferences", element: }, + { path: "*", element: } +]; diff --git a/starsky/starsky/clientapp/src/shared/global-shortcuts/global-shortcuts.spec.tsx b/starsky/starsky/clientapp/src/shared/global-shortcuts/global-shortcuts.spec.tsx new file mode 100644 index 0000000000..ecca93b6ab --- /dev/null +++ b/starsky/starsky/clientapp/src/shared/global-shortcuts/global-shortcuts.spec.tsx @@ -0,0 +1,24 @@ +import * as useHotKeysParent from "../../hooks/use-keyboard/use-hotkeys"; +import * as useLocation from "../../hooks/use-location/use-location"; +import { GlobalShortcuts } from "./global-shortcuts"; + +describe("GlobalShortcuts", () => { + it("should call useHotKeys with the correct arguments", () => { + const locationObject = { + location: window.location, + navigate: jest.fn() + }; + + jest.spyOn(useLocation, "default").mockImplementationOnce(() => locationObject); + + GlobalShortcuts(); + + expect(useHotKeysParent).toHaveBeenCalledWith( + { key: "k", shiftKey: true, ctrlKeyOrMetaKey: true }, + expect.any(Function), + [] + ); + + expect(locationObject.navigate).toHaveBeenCalledWith(expect.any(String)); + }); +}); diff --git a/starsky/starsky/clientapp/src/shared/global-shortcuts/global-shortcuts.tsx b/starsky/starsky/clientapp/src/shared/global-shortcuts/global-shortcuts.tsx new file mode 100644 index 0000000000..cf7b0b2c16 --- /dev/null +++ b/starsky/starsky/clientapp/src/shared/global-shortcuts/global-shortcuts.tsx @@ -0,0 +1,16 @@ +import useHotKeys from "../../hooks/use-keyboard/use-hotkeys"; +import useLocation from "../../hooks/use-location/use-location"; +import { UrlQuery } from "../url-query"; + +export function GlobalShortcuts() { + const history = useLocation(); + + // used in desktop to route from menu + useHotKeys( + { key: "k", shiftKey: true, ctrlKeyOrMetaKey: true }, + () => { + history.navigate(new UrlQuery().UrlPreferencesPage()); + }, + [] + ); +} diff --git a/starskydesktop/src/app/edit-file/edit-file.ts b/starskydesktop/src/app/edit-file/edit-file.ts index 403b381d76..f0c899d3b7 100644 --- a/starskydesktop/src/app/edit-file/edit-file.ts +++ b/starskydesktop/src/app/edit-file/edit-file.ts @@ -4,15 +4,12 @@ import { GetBaseUrlFromSettings } from "../config/get-base-url-from-settings"; import UrlQuery from "../config/url-query"; import { createErrorWindow } from "../error-window/create-error-window"; import logger from "../logger/logger"; -import { - GetNetRequest, - IGetNetRequestResponse, -} from "../net-request/get-net-request"; +import { GetNetRequest, IGetNetRequestResponse } from "../net-request/get-net-request"; import { createParentFolders } from "./create-parent-folders"; import { downloadBinary } from "./download-binary"; import { downloadXmpFile } from "./download-xmp-file"; import { IsDetailViewResult } from "./is-detail-view-result"; -import { openPath } from "./open-path"; +import { OpenPath } from "./open-path"; function getFilePathFromWindow(fromMainWindow: BrowserWindow): string { const latestPage = fromMainWindow.webContents.getURL(); @@ -23,7 +20,7 @@ function getFilePathFromWindow(fromMainWindow: BrowserWindow): string { async function openWindow(filePathOnDisk: string) { try { - await openPath(filePathOnDisk); + await OpenPath(filePathOnDisk); } catch (error: unknown) { // eslint-disable-next-line @typescript-eslint/no-floating-promises createErrorWindow(error as string); @@ -56,9 +53,6 @@ export async function EditFile(fromMainWindow: BrowserWindow) { await createParentFolders(fileIndexItem.parentDirectory); await downloadXmpFile(fileIndexItem, fromMainWindow.webContents.session); - const filePathOnDisk = await downloadBinary( - fileIndexItem, - fromMainWindow.webContents.session - ); + const filePathOnDisk = await downloadBinary(fileIndexItem, fromMainWindow.webContents.session); await openWindow(filePathOnDisk); } diff --git a/starskydesktop/src/app/edit-file/open-path.ts b/starskydesktop/src/app/edit-file/open-path.ts index d4ccbecdad..40e7a2cb37 100644 --- a/starskydesktop/src/app/edit-file/open-path.ts +++ b/starskydesktop/src/app/edit-file/open-path.ts @@ -1,74 +1,12 @@ -import * as childProcess from "child_process"; import { shell } from "electron"; -import * as appConfig from "electron-settings"; -import DefaultImageApplicationSetting from "../config/default-image-application-settings"; import logger from "../logger/logger"; -import OsBuildKey from "../os-info/os-build-key"; -import { IsApplicationRunning } from "./is-application-running"; -/** - * @see: https://community.adobe.com/t5/photoshop/problems-opening-photoshop-open-a/m-p/11541937?page=1 - * Since nobody cares - */ -async function ShouldRunFirst() { - return IsApplicationRunning(".app/Contents/MacOS/Adobe\\ Photoshop"); -} - -function openWindows( - overWriteDefaultApplication: string, - fullFilePath: string -) { - // need to check if fullFilePath is file - const openWin = `"${overWriteDefaultApplication}" "${fullFilePath}"`; - childProcess.exec(openWin); -} - -function openMac(overWriteDefaultApplication: string, fullFilePath: string) { - const openFileOnMac = `open -a "${overWriteDefaultApplication}" "${fullFilePath}"`; - - // // need to check if fullFilePath is directory - childProcess.exec(openFileOnMac, { - cwd: `${overWriteDefaultApplication}` - }); -} - -export async function openPath(fullFilePath: string): Promise { - const overWriteDefaultApplication = (await appConfig.get( - DefaultImageApplicationSetting - )) as string; - // eslint-disable-next-line @typescript-eslint/no-misused-promises, no-async-promise-executor - return new Promise(async (resolve, reject) => { - // add extra test for photoshop - if ( - overWriteDefaultApplication - && OsBuildKey() === "mac" - && overWriteDefaultApplication.includes("Adobe Photoshop") - ) { - const shouldRunFirst = await ShouldRunFirst(); - if (!shouldRunFirst) { - // eslint-disable-next-line prefer-promise-reject-errors - reject( - "Photoshop is not running, please start photoshop first and try it again" - ); - return; - } - } - - if (overWriteDefaultApplication && OsBuildKey() === "mac") { - openMac(overWriteDefaultApplication, fullFilePath); - resolve(); - return; - } - - if (overWriteDefaultApplication && OsBuildKey() === "win") { - openWindows(overWriteDefaultApplication, fullFilePath); - resolve(); - return; - } +export async function OpenPath(fullFilePath: string): Promise { + return new Promise((resolve) => { logger.info("open default", fullFilePath); // eslint-disable-next-line @typescript-eslint/no-floating-promises shell.openPath(fullFilePath).then(() => { - resolve(); + resolve(true); }); }); } diff --git a/starskydesktop/src/app/ipc-bridge/ipc-bridge.spec.ts b/starskydesktop/src/app/ipc-bridge/ipc-bridge.spec.ts index 53d44f10ff..5e5af62e98 100644 --- a/starskydesktop/src/app/ipc-bridge/ipc-bridge.spec.ts +++ b/starskydesktop/src/app/ipc-bridge/ipc-bridge.spec.ts @@ -4,21 +4,15 @@ import { BrowserWindow, net } from "electron"; import * as appConfig from "electron-settings"; import { AppVersionIpcKey } from "../config/app-version-ipc-key.const"; -import { DefaultImageApplicationIpcKey } from "../config/default-image-application-settings-ipc-key.const"; import * as GetBaseUrlFromSettings from "../config/get-base-url-from-settings"; -import { - LocationIsRemoteIpcKey, - LocationUrlIpcKey, -} from "../config/location-ipc-keys.const"; +import { LocationIsRemoteIpcKey, LocationUrlIpcKey } from "../config/location-ipc-keys.const"; import { UpdatePolicyIpcKey } from "../config/update-policy-ipc-key.const"; -import * as fileSelectorWindow from "../file-selector-window/file-selector-window"; import * as SetupFileWatcher from "../file-watcher/setup-file-watcher"; import * as logger from "../logger/logger"; import * as createMainWindow from "../main-window/create-main-window"; import { mainWindows } from "../main-window/main-windows.const"; import { AppVersionCallback, - DefaultImageApplicationCallback, LocationIsRemoteCallback, LocationUrlCallback, UpdatePolicyCallback, @@ -88,9 +82,7 @@ describe("ipc bridge", () => { jest .spyOn(createMainWindow, "default") - .mockImplementationOnce(() => - Promise.resolve({ once: jest.fn() } as any) - ); + .mockImplementationOnce(() => Promise.resolve({ once: jest.fn() } as any)); jest .spyOn(SetupFileWatcher, "SetupFileWatcher") @@ -114,9 +106,7 @@ describe("ipc bridge", () => { jest .spyOn(createMainWindow, "default") - .mockImplementationOnce(() => - Promise.resolve({ once: jest.fn() } as any) - ); + .mockImplementationOnce(() => Promise.resolve({ once: jest.fn() } as any)); jest .spyOn(SetupFileWatcher, "SetupFileWatcher") @@ -180,15 +170,13 @@ describe("ipc bridge", () => { jest.spyOn(appConfig, "get").mockImplementationOnce(() => { return Promise.resolve(true); }); - jest - .spyOn(GetBaseUrlFromSettings, "GetBaseUrlFromSettings") - .mockImplementationOnce(() => { - return Promise.resolve({ - isLocal: true, - isValid: null, - location: "http://localhost:9609", - }); + jest.spyOn(GetBaseUrlFromSettings, "GetBaseUrlFromSettings").mockImplementationOnce(() => { + return Promise.resolve({ + isLocal: true, + isValid: null, + location: "http://localhost:9609", }); + }); await LocationUrlCallback(event, null); expect(event.reply).toHaveBeenCalled(); expect(event.reply).toHaveBeenCalledWith(LocationUrlIpcKey, { @@ -208,15 +196,13 @@ describe("ipc bridge", () => { jest.spyOn(appConfig, "get").mockImplementationOnce(() => { return Promise.resolve("__url_from_config__"); }); - jest - .spyOn(GetBaseUrlFromSettings, "GetBaseUrlFromSettings") - .mockImplementationOnce(() => { - return Promise.resolve({ - isLocal: false, - isValid: null, - location: "__url_from_config__", - }); + jest.spyOn(GetBaseUrlFromSettings, "GetBaseUrlFromSettings").mockImplementationOnce(() => { + return Promise.resolve({ + isLocal: false, + isValid: null, + location: "__url_from_config__", }); + }); await LocationUrlCallback(event, null); expect(event.reply).toHaveBeenCalled(); @@ -426,92 +412,4 @@ describe("ipc bridge", () => { expect(event.reply).toHaveBeenCalledWith(UpdatePolicyIpcKey, false); }); }); - - describe("DefaultImageApplicationCallback", () => { - it("getting with null input (DefaultImageApplicationCallback)", async () => { - const event = { reply: jest.fn() } as unknown as Electron.IpcMainEvent; - - jest.spyOn(appConfig, "get").mockReset(); - jest - .spyOn(appConfig, "get") - .mockImplementationOnce(() => Promise.resolve(null)); - await DefaultImageApplicationCallback(event, null); - expect(event.reply).toHaveBeenCalled(); - expect(event.reply).toHaveBeenCalledWith( - DefaultImageApplicationIpcKey, - null - ); - }); - - it("set reset of DefaultImageApplicationCallback", async () => { - const event = { reply: jest.fn() } as unknown as Electron.IpcMainEvent; - - jest.spyOn(appConfig, "get").mockImplementationOnce(() => { - return Promise.resolve(true); - }); - - jest.spyOn(appConfig, "set").mockImplementationOnce(() => { - return Promise.resolve(); - }); - - await DefaultImageApplicationCallback(event, { reset: true }); - - expect(event.reply).toHaveBeenCalled(); - expect(event.reply).toHaveBeenCalledWith( - DefaultImageApplicationIpcKey, - false - ); - }); - - it("should give successfull showOpenDialog", async () => { - const event = { reply: jest.fn() } as unknown as Electron.IpcMainEvent; - - jest.spyOn(appConfig, "get").mockImplementationOnce(() => { - return Promise.resolve(true); - }); - - jest - .spyOn(fileSelectorWindow, "fileSelectorWindow") - .mockImplementationOnce(() => - Promise.resolve(["result_from_fileSelectorWindow"]) - ); - - jest.spyOn(appConfig, "set").mockImplementationOnce(() => { - return Promise.resolve(); - }); - - await DefaultImageApplicationCallback(event, { showOpenDialog: true }); - - expect(event.reply).toHaveBeenCalled(); - expect(event.reply).toHaveBeenCalledWith( - DefaultImageApplicationIpcKey, - "result_from_fileSelectorWindow" - ); - }); - - it("should ignore failing showOpenDialog", async () => { - const event = { reply: jest.fn() } as unknown as Electron.IpcMainEvent; - - jest.spyOn(appConfig, "get").mockImplementationOnce(() => { - return Promise.resolve(true); - }); - - jest - .spyOn(fileSelectorWindow, "fileSelectorWindow") - // eslint-disable-next-line prefer-promise-reject-errors - .mockImplementationOnce(() => - Promise.reject(["result_from_fileSelectorWindow"]) - ); - - jest.spyOn(appConfig, "set").mockImplementationOnce(() => { - return Promise.resolve(); - }); - - await DefaultImageApplicationCallback(event, { - showOpenDialog: true, - }); - - expect(event.reply).toHaveBeenCalledTimes(0); - }); - }); }); diff --git a/starskydesktop/src/app/ipc-bridge/ipc-bridge.ts b/starskydesktop/src/app/ipc-bridge/ipc-bridge.ts index 762899e3f0..2036f53322 100644 --- a/starskydesktop/src/app/ipc-bridge/ipc-bridge.ts +++ b/starskydesktop/src/app/ipc-bridge/ipc-bridge.ts @@ -3,26 +3,17 @@ import { app, ipcMain } from "electron"; import * as appConfig from "electron-settings"; import { IlocationUrlSettings } from "../config/IlocationUrlSettings"; import { AppVersionIpcKey } from "../config/app-version-ipc-key.const"; -import DefaultImageApplicationSetting from "../config/default-image-application-settings"; -import { - DefaultImageApplicationIpcKey, - IDefaultImageApplicationProps -} from "../config/default-image-application-settings-ipc-key.const"; import { GetBaseUrlFromSettings } from "../config/get-base-url-from-settings"; -import { - LocationIsRemoteIpcKey, - LocationUrlIpcKey -} from "../config/location-ipc-keys.const"; +import { LocationIsRemoteIpcKey, LocationUrlIpcKey } from "../config/location-ipc-keys.const"; import { LocationIsRemoteSettingsKey, - LocationUrlSettingsKey + LocationUrlSettingsKey, } from "../config/location-settings.const"; import RememberUrl from "../config/remember-url-settings.const"; import { UpdatePolicyIpcKey } from "../config/update-policy-ipc-key.const"; import { UpdatePolicySettings } from "../config/update-policy-settings.const"; import UrlQuery from "../config/url-query"; import { ipRegex, urlRegex } from "../config/url-regex"; -import { fileSelectorWindow } from "../file-selector-window/file-selector-window"; import { SetupFileWatcher } from "../file-watcher/setup-file-watcher"; import logger from "../logger/logger"; import createMainWindow from "../main-window/create-main-window"; @@ -31,15 +22,10 @@ import { GetNetRequest } from "../net-request/get-net-request"; import { settingsWindows } from "../settings-window/settings-windows.const"; import { IsRemote } from "../warmup/is-remote"; -export async function UpdatePolicyCallback( - event: Electron.IpcMainEvent, - args: boolean, -) { +export async function UpdatePolicyCallback(event: Electron.IpcMainEvent, args: boolean) { if (args === null || args === undefined) { if (await appConfig.has(UpdatePolicySettings)) { - const updatePolicy = (await appConfig.get( - UpdatePolicySettings, - )) as boolean; + const updatePolicy = (await appConfig.get(UpdatePolicySettings)) as boolean; if (updatePolicy !== null && updatePolicy !== undefined) { event.reply(UpdatePolicyIpcKey, updatePolicy); @@ -73,10 +59,7 @@ async function closeAndCreateNewWindow() { }); } -export async function LocationIsRemoteCallback( - event: Electron.IpcMainEvent, - args: boolean, -) { +export async function LocationIsRemoteCallback(event: Electron.IpcMainEvent, args: boolean) { if (args !== undefined && args !== null) { await appConfig.set(LocationIsRemoteSettingsKey, args.toString()); // filewatcher need to be after update/set @@ -88,37 +71,26 @@ export async function LocationIsRemoteCallback( } export function AppVersionCallback(event: Electron.IpcMainEvent) { - const appVersion = app - .getVersion() - .match(/^[0-9]+\.[0-9]+/ig); + const appVersion = app.getVersion().match(/^[0-9]+\.[0-9]+/gi); event.reply(AppVersionIpcKey, appVersion); } -export async function LocationUrlCallback( - event: Electron.IpcMainEvent, - args: string, -) { +export async function LocationUrlCallback(event: Electron.IpcMainEvent, args: string) { // getting if (!args) { event.reply(LocationUrlIpcKey, await GetBaseUrlFromSettings()); return; } - if ( - args.match(urlRegex) - || args.match(ipRegex) - || args.startsWith("http://localhost:") - ) { + if (args.match(urlRegex) || args.match(ipRegex) || args.startsWith("http://localhost:")) { console.log("ipc-bridge start update"); // to avoid errors const locationUrl = args.replace(/\/$/, ""); try { - const response = await GetNetRequest( - locationUrl + new UrlQuery().HealthApi(), - ); + const response = await GetNetRequest(locationUrl + new UrlQuery().HealthApi()); const responseSettings = { location: locationUrl, isLocal: false, @@ -156,8 +128,13 @@ export async function LocationUrlCallback( } return; } - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - console.log(`ipc-bridge ${args.match(urlRegex)} ${args.match(ipRegex)} ${args.startsWith("http://localhost:")}`); + + console.log( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `ipc-bridge ${args.match(urlRegex)} ${args.match(ipRegex)} ${args.startsWith( + "http://localhost:" + )}` + ); event.reply(LocationUrlIpcKey, { isValid: false, @@ -166,43 +143,16 @@ export async function LocationUrlCallback( } as IlocationUrlSettings); } -export async function DefaultImageApplicationCallback( - event: Electron.IpcMainEvent, - args: IDefaultImageApplicationProps, -) { - if (!args) { - const currentSettings = await appConfig.get(DefaultImageApplicationSetting); - event.reply(DefaultImageApplicationIpcKey, currentSettings); - return; - } - if (args.reset) { - await appConfig.unset(DefaultImageApplicationSetting); - event.reply(DefaultImageApplicationIpcKey, false); - return; - } - - if (args.showOpenDialog) { - try { - const result = await fileSelectorWindow(); - await appConfig.set(DefaultImageApplicationSetting, result[0]); - event.reply(DefaultImageApplicationIpcKey, result[0]); - } catch (error) { // nothing here - } - } -} - function ipcBridge() { // When adding a new key also update preload-main.ts - ipcMain.on(LocationIsRemoteIpcKey, async (event, args : boolean) => LocationIsRemoteCallback(event, args)); + ipcMain.on(LocationIsRemoteIpcKey, async (event, args: boolean) => LocationIsRemoteCallback(event, args)); ipcMain.on(AppVersionIpcKey, (event) => AppVersionCallback(event)); ipcMain.on(LocationUrlIpcKey, async (event, args: string) => LocationUrlCallback(event, args)); ipcMain.on(UpdatePolicyIpcKey, async (event, args: boolean) => UpdatePolicyCallback(event, args)); - - ipcMain.on(DefaultImageApplicationIpcKey, async (event, args : IDefaultImageApplicationProps) => DefaultImageApplicationCallback(event, args)); } export default ipcBridge; diff --git a/starskydesktop/src/app/main-window/create-main-window.spec.ts b/starskydesktop/src/app/main-window/create-main-window.spec.ts index 391e52c62b..c3839952e9 100644 --- a/starskydesktop/src/app/main-window/create-main-window.spec.ts +++ b/starskydesktop/src/app/main-window/create-main-window.spec.ts @@ -3,7 +3,6 @@ import * as windowStateKeeper from "../window-state-keeper/window-state-keeper"; import createMainWindow from "./create-main-window"; import * as getNewFocusedWindow from "./get-new-focused-window"; -import * as onHeaderReceived from "./on-headers-received"; import * as saveRememberUrl from "./save-remember-url"; import * as spellCheck from "./spellcheck"; @@ -16,7 +15,20 @@ jest.mock("electron", () => { on: () => "en", }, // eslint-disable-next-line object-shorthand, func-names, @typescript-eslint/no-unused-vars - BrowserWindow: function (_x:object, _y: number, _w: number, _h: number, _s: boolean, _w2: object) { + BrowserWindow: function ( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _x: object, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _y: number, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _w: number, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _h: number, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _s: boolean, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _w2: object + ) { return { id: 99, loadFile: jest.fn(), @@ -38,7 +50,6 @@ jest.mock("electron", () => { }; }, __esModule: true, - }; }); @@ -51,27 +62,21 @@ jest.mock("electron-settings", () => { describe("create main window", () => { it("create a new window (main)", async () => { - jest - .spyOn(windowStateKeeper, "windowStateKeeper") - .mockImplementationOnce(() => Promise.resolve({ - x: 0, - y: 0, - width: 1, - height: 1, - isMaximized: false, - track: jest.fn(), - })); + jest.spyOn(windowStateKeeper, "windowStateKeeper").mockImplementationOnce(() => Promise.resolve({ + x: 0, + y: 0, + width: 1, + height: 1, + isMaximized: false, + track: jest.fn(), + })); jest .spyOn(getNewFocusedWindow, "getNewFocusedWindow") .mockImplementationOnce(() => ({ x: 1, y: 1 })); - jest - .spyOn(onHeaderReceived, "onHeaderReceived") - .mockImplementationOnce(() => null); + jest.spyOn(spellCheck, "spellCheck").mockImplementationOnce(() => null); - jest - .spyOn(saveRememberUrl, "removeRememberUrl") - .mockImplementationOnce(() => null); + jest.spyOn(saveRememberUrl, "removeRememberUrl").mockImplementationOnce(() => null); jest .spyOn(saveRememberUrl, "saveRememberUrl") diff --git a/starskydesktop/src/app/main-window/create-main-window.ts b/starskydesktop/src/app/main-window/create-main-window.ts index 5cd81cbcaf..b4037c5ff3 100644 --- a/starskydesktop/src/app/main-window/create-main-window.ts +++ b/starskydesktop/src/app/main-window/create-main-window.ts @@ -4,7 +4,6 @@ import { GetAppVersion } from "../config/get-app-version"; import { windowStateKeeper } from "../window-state-keeper/window-state-keeper"; import { getNewFocusedWindow } from "./get-new-focused-window"; import { mainWindows } from "./main-windows.const"; -import { onHeaderReceived } from "./on-headers-received"; import { removeRememberUrl, saveRememberUrl } from "./save-remember-url"; import { spellCheck } from "./spellcheck"; @@ -37,15 +36,6 @@ async function createMainWindow(openSpecificUrl: string, offset = 0): Promise
{ - // logger.info(result); - // }) - // .catch((result) => { - // logger.info(result); - // }); - mainWindowStateKeeper.track(newWindow); const location = path.join(__dirname, "client/pages/redirect/reload-redirect.html"); @@ -55,7 +45,6 @@ async function createMainWindow(openSpecificUrl: string, offset = 0): Promise
{ newWindow.show(); diff --git a/starskydesktop/src/app/menu/app-menu.ts b/starskydesktop/src/app/menu/app-menu.ts index cf25562f3a..dd2c14ee88 100644 --- a/starskydesktop/src/app/menu/app-menu.ts +++ b/starskydesktop/src/app/menu/app-menu.ts @@ -1,12 +1,12 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import { - app, BrowserWindow, Menu, shell -} from "electron"; +import { app, BrowserWindow, Menu, shell } from "electron"; +import { EditFile } from "../edit-file/edit-file"; import { IsDutch } from "../i18n/i18n"; import createMainWindow from "../main-window/create-main-window"; import { createSettingsWindow } from "../settings-window/create-settings-window"; +import { IsRemote } from "../warmup/is-remote"; -function sendKeybinding(win: BrowserWindow, keyCode: string, cmdOrCtrl: boolean) { +function sendKeybinding(win: BrowserWindow, keyCode: string, cmdOrCtrl: boolean, shift: boolean) { const modifiers = []; if (cmdOrCtrl) { @@ -17,6 +17,9 @@ function sendKeybinding(win: BrowserWindow, keyCode: string, cmdOrCtrl: boolean) modifiers.push("ctrl"); } } + if (shift) { + modifiers.push("shift"); + } win.webContents.sendInputEvent({ type: "keyDown", modifiers, keyCode }); win.webContents.sendInputEvent({ type: "char", modifiers, keyCode }); @@ -29,36 +32,36 @@ function AppMenu() { const menu = Menu.buildFromTemplate([ ...(isMac ? [ - { - label: app.name, - submenu: [ - { - label: IsDutch() ? "Over Starsky" : "About Starsky", - role: "about", - }, - { type: "separator" }, - { role: "services" }, - { type: "separator" }, - { - label: IsDutch() ? "Verberg Starsky" : "Hide Starsky", - role: "hide", - }, - { - label: IsDutch() ? "Verberg andere" : "Hide Others", - role: "hideothers", - }, - { - label: IsDutch() ? "Toon alles" : "Show All", - role: "unhide", - }, - { type: "separator" }, - { - label: IsDutch() ? "Starsky afsluiten" : "Quit Starsky", - role: "quit", - }, - ] as any, - }, - ] + { + label: app.name, + submenu: [ + { + label: IsDutch() ? "Over Starsky" : "About Starsky", + role: "about", + }, + { type: "separator" }, + { role: "services" }, + { type: "separator" }, + { + label: IsDutch() ? "Verberg Starsky" : "Hide Starsky", + role: "hide", + }, + { + label: IsDutch() ? "Verberg andere" : "Hide Others", + role: "hideothers", + }, + { + label: IsDutch() ? "Toon alles" : "Show All", + role: "unhide", + }, + { type: "separator" }, + { + label: IsDutch() ? "Starsky afsluiten" : "Quit Starsky", + role: "quit", + }, + ] as any, + }, + ] : []), { label: IsDutch() ? "Bestand" : "File", @@ -75,28 +78,28 @@ function AppMenu() { label: IsDutch() ? "Bewerk bestand in editor" : "Edit file in Editor", click: () => { const focusWindow = BrowserWindow.getFocusedWindow(); + // eslint-disable-next-line @typescript-eslint/no-floating-promises + IsRemote().then(async (isRemote) => { + if (!isRemote) { + sendKeybinding(focusWindow, "e", true, false); + return; + } - sendKeybinding(focusWindow, "e", true); - - // focusWindow.webContents.sendInputEvent({ - // keyCode: "CommandOrControl+e", - // type: "keyDown", - // }); - // // eslint-disable-next-line @typescript-eslint/no-floating-promises - // if (focusWindow) EditFile(focusWindow); + if (focusWindow) await EditFile(focusWindow).catch(() => {}); + }); }, accelerator: "CmdOrCtrl+E", }, isMac ? { - label: IsDutch() ? "Venster sluiten" : "Close Window", - role: "close", - } + label: IsDutch() ? "Venster sluiten" : "Close Window", + role: "close", + } : { - label: IsDutch() ? "App sluiten" : "Close App", - role: "quit", - }, + label: IsDutch() ? "App sluiten" : "Close App", + role: "quit", + }, ], }, { @@ -139,13 +142,21 @@ function AppMenu() { label: IsDutch() ? "Instellingen" : "Settings", submenu: [ { - label: IsDutch() ? "Instellingen" : "Settings", + label: IsDutch() ? "Verbindings instellingen" : "Connection Settings", click: () => { // eslint-disable-next-line @typescript-eslint/no-floating-promises createSettingsWindow(); }, accelerator: "CmdOrCtrl+,", }, + { + label: IsDutch() ? "App instellingen" : "App Settings", + click: () => { + const focusWindow = BrowserWindow.getFocusedWindow(); + sendKeybinding(focusWindow, "k", true, true); + }, + accelerator: "CmdOrCtrl+shift+k", + }, ], }, { diff --git a/starskydesktop/src/client/pages/settings/settings.html b/starskydesktop/src/client/pages/settings/settings.html index 410dd3ab9d..413f50222c 100644 --- a/starskydesktop/src/client/pages/settings/settings.html +++ b/starskydesktop/src/client/pages/settings/settings.html @@ -46,18 +46,6 @@ -
Select default application
-
-   - -

 

-
- Loading... -
-
Check for updates
diff --git a/starskydesktop/src/main/main.ts b/starskydesktop/src/main/main.ts index 3f9dc704f8..ef757d9af7 100755 --- a/starskydesktop/src/main/main.ts +++ b/starskydesktop/src/main/main.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ -import { app, BrowserWindow, protocol } from "electron"; +import { app, BrowserWindow } from "electron"; import * as os from "os"; -import logger from "src/app/logger/logger"; import { setupChildProcess } from "../app/child-process/setup-child-process"; import { MakeLogsPath } from "../app/config/logs-path"; import { MakeTempPath } from "../app/config/temp-path"; @@ -23,18 +22,6 @@ setupChildProcess(); MakeTempPath(); SetupFileWatcher(); -protocol.registerSchemesAsPrivileged([ - { - scheme: "app", - privileges: { - supportFetchAPI: true, - bypassCSP: true, - standard: true, - secure: true, - }, - }, -]); - console.log(`running in: :${os.arch()}`); // This method will be called when Electron has finished @@ -44,13 +31,6 @@ app.on("ready", () => { AppMenu(); DockMenu(); - protocol.handle("app", (req) => { - logger.info(req.url); - return new Response("

hello, world

", { - headers: { "content-type": "text/html" }, - }); - }); - IsRemote().then(async (isRemote) => { const splashWindows = await SetupSplash(); RestoreWarmupMainWindowAndCloseSplash(splashWindows, isRemote); From 5d8a2369cd669e4d443e1d941b63e1a4d0528143 Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 22 Feb 2024 22:38:44 +0100 Subject: [PATCH 093/125] add comment --- .../clientapp/src/shared/global-shortcuts/global-shortcuts.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/starsky/starsky/clientapp/src/shared/global-shortcuts/global-shortcuts.tsx b/starsky/starsky/clientapp/src/shared/global-shortcuts/global-shortcuts.tsx index cf7b0b2c16..b0dfc47fe2 100644 --- a/starsky/starsky/clientapp/src/shared/global-shortcuts/global-shortcuts.tsx +++ b/starsky/starsky/clientapp/src/shared/global-shortcuts/global-shortcuts.tsx @@ -6,6 +6,7 @@ export function GlobalShortcuts() { const history = useLocation(); // used in desktop to route from menu + // command + shift + k useHotKeys( { key: "k", shiftKey: true, ctrlKeyOrMetaKey: true }, () => { From c0be912b303e39810ac0435865ebdc1d6bafdcb3 Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 22 Feb 2024 22:42:12 +0100 Subject: [PATCH 094/125] add test --- .../global-shortcuts.spec.tsx | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/starsky/starsky/clientapp/src/shared/global-shortcuts/global-shortcuts.spec.tsx b/starsky/starsky/clientapp/src/shared/global-shortcuts/global-shortcuts.spec.tsx index ecca93b6ab..d0791dea24 100644 --- a/starsky/starsky/clientapp/src/shared/global-shortcuts/global-shortcuts.spec.tsx +++ b/starsky/starsky/clientapp/src/shared/global-shortcuts/global-shortcuts.spec.tsx @@ -1,24 +1,42 @@ +import React from "react"; import * as useHotKeysParent from "../../hooks/use-keyboard/use-hotkeys"; import * as useLocation from "../../hooks/use-location/use-location"; +import { UrlQuery } from "../url-query"; import { GlobalShortcuts } from "./global-shortcuts"; describe("GlobalShortcuts", () => { - it("should call useHotKeys with the correct arguments", () => { + it("command + shift + k", () => { const locationObject = { location: window.location, navigate: jest.fn() }; - jest.spyOn(useLocation, "default").mockImplementationOnce(() => locationObject); + jest.spyOn(React, "useEffect").mockImplementationOnce((cb) => { + cb(); + }); - GlobalShortcuts(); + const event = new KeyboardEvent("keydown", { + bubbles: true, + cancelable: true, + key: "k", + metaKey: true, + shiftKey: true + }); + + jest.spyOn(useHotKeysParent, "default").mockImplementationOnce((_, callback) => { + if (callback) { + callback(event); + } + }); - expect(useHotKeysParent).toHaveBeenCalledWith( - { key: "k", shiftKey: true, ctrlKeyOrMetaKey: true }, - expect.any(Function), - [] - ); + const useLocationSpy = jest + .spyOn(useLocation, "default") + .mockImplementationOnce(() => locationObject); + + GlobalShortcuts(); - expect(locationObject.navigate).toHaveBeenCalledWith(expect.any(String)); + expect(useLocationSpy).toHaveBeenCalled(); + expect(locationObject.navigate).toHaveBeenCalled(); + expect(locationObject.navigate).toHaveBeenCalledWith(new UrlQuery().UrlPreferencesPage()); }); }); From bdd09fb87ce7f16d63f76a442c417b107064d0e6 Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 22 Feb 2024 23:43:05 +0100 Subject: [PATCH 095/125] add check if feature exists --- ...menu-option-desktop-editor-open-selection.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.tsx index de1fa748a1..1a1ee0cfb5 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.tsx @@ -51,10 +51,14 @@ export async function StartMenuOptionDesktopEditorOpenSelection( setIsError: React.Dispatch>, messageDesktopEditorUnableToOpen: string, setModalConfirmationOpenFiles: (value: React.SetStateAction) => void, - isReadOnly: boolean + isReadOnly: boolean, + editorEnabled: boolean ) { + if (editorEnabled !== true || isReadOnly) { + return; + } const toDesktopOpenList = new URLPath().MergeSelectFileIndexItem(select, state.fileIndexItems); - if (!toDesktopOpenList || toDesktopOpenList.length === 0 || isReadOnly) return; + if (!toDesktopOpenList || toDesktopOpenList.length === 0) return; const selectParams = new URLPath().ArrayToCommaSeparatedStringOneParent(toDesktopOpenList, ""); const urlCheck = new UrlQuery().UrlApiDesktopEditorOpenAmountConfirmationChecker(); @@ -94,7 +98,7 @@ const MenuOptionDesktopEditorOpenSelection: React.FunctionComponent { StartMenuOptionDesktopEditorOpenSelection( @@ -104,7 +108,8 @@ const MenuOptionDesktopEditorOpenSelection: React.FunctionComponent { // do nothing }); @@ -144,7 +149,8 @@ const MenuOptionDesktopEditorOpenSelection: React.FunctionComponent Date: Thu, 22 Feb 2024 23:55:37 +0100 Subject: [PATCH 096/125] add inital setup --- ...ditor-open-selection-no-select-warning.tsx | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.tsx diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.tsx new file mode 100644 index 0000000000..5b639d91ea --- /dev/null +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.tsx @@ -0,0 +1,28 @@ +import { memo } from "react"; +import useFetch from "../../../hooks/use-fetch"; +import useHotKeys from "../../../hooks/use-keyboard/use-hotkeys"; +import { IEnvFeatures } from "../../../interfaces/IEnvFeatures"; +import { UrlQuery } from "../../../shared/url-query"; + +interface IMenuOptionDesktopEditorOpenSelectionNoSelectWarningProps { + select: string[]; + isReadonly: boolean; +} + +const MenuOptionDesktopEditorOpenSelectionNoSelectWarning: React.FunctionComponent = + memo(({ select, isReadonly }) => { + // Check API to know if feature is needed! + const featuresResult = useFetch(new UrlQuery().UrlApiFeaturesAppSettings(), "get"); + const dataFeatures = featuresResult?.data as IEnvFeatures | undefined; + + /** + * Open editor with keys - command + e + */ + useHotKeys({ key: "e", ctrlKeyOrMetaKey: true }, () => { + if (dataFeatures?.openEditorEnabled !== true || isReadonly || select.length >= 1) return; + }); + + return <>; + }); + +export default MenuOptionDesktopEditorOpenSelectionNoSelectWarning; From db1872b53efd6fe0c550a3db84f9d3164dc9e3ae Mon Sep 17 00:00:00 2001 From: Dion Date: Thu, 22 Feb 2024 23:55:52 +0100 Subject: [PATCH 097/125] setup --- .../menu-option-desktop-editor-open-selection.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.tsx index 1a1ee0cfb5..4a444ca8af 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.tsx @@ -51,10 +51,9 @@ export async function StartMenuOptionDesktopEditorOpenSelection( setIsError: React.Dispatch>, messageDesktopEditorUnableToOpen: string, setModalConfirmationOpenFiles: (value: React.SetStateAction) => void, - isReadOnly: boolean, - editorEnabled: boolean + isReadOnly: boolean ) { - if (editorEnabled !== true || isReadOnly) { + if (isReadOnly) { return; } const toDesktopOpenList = new URLPath().MergeSelectFileIndexItem(select, state.fileIndexItems); @@ -108,8 +107,7 @@ const MenuOptionDesktopEditorOpenSelection: React.FunctionComponent { // do nothing }); @@ -149,8 +147,7 @@ const MenuOptionDesktopEditorOpenSelection: React.FunctionComponent Date: Thu, 22 Feb 2024 22:57:02 +0100 Subject: [PATCH 098/125] add tests --- ...-open-selection-no-select-warning.spec.tsx | 44 ++++++++++++++++++ ...en-selection-no-select-warning.stories.tsx | 9 ++++ ...ditor-open-selection-no-select-warning.tsx | 23 +++++++--- ...ion-desktop-editor-open-selection.spec.tsx | 45 +++++++++++++++++++ ...u-option-desktop-editor-open-selection.tsx | 5 ++- .../organisms/menu-archive/menu-archive.tsx | 9 ++++ 6 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.spec.tsx create mode 100644 starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.stories.tsx diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.spec.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.spec.tsx new file mode 100644 index 0000000000..ce6174e005 --- /dev/null +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.spec.tsx @@ -0,0 +1,44 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import MenuOptionDesktopEditorOpenSelectionNoSelectWarning from "./menu-option-desktop-editor-open-selection-no-select-warning"; + +describe("MenuOptionDesktopEditorOpenSelectionNoSelectWarning", () => { + it("should render without crashing", () => { + render(); + }); + + it("should show error notification when trying to open editor without selecting anything", () => { + render(); + fireEvent.keyDown(window, { key: "e", ctrlKey: true }); + expect(screen.getByText("select first")).toBeTruthy(); + }); + + it("should not show error notification when select is not empty", () => { + render( + + ); + fireEvent.keyDown(window, { key: "e", ctrlKey: true }); + expect(screen.queryByText("select first")).toBeNull(); + }); + + it("should not show error notification when read-only mode is enabled", () => { + render(); + fireEvent.keyDown(window, { key: "e", ctrlKey: true }); + expect(screen.queryByText("select first")).toBeNull(); + }); + + it("should not show error notification when editor feature is disabled", () => { + jest.spyOn(window, "fetch").mockImplementation(() => ({ + json: async () => ({ openEditorEnabled: false }) + })); + render(); + fireEvent.keyDown(window, { key: "e", ctrlKey: true }); + expect(screen.queryByText("select first")).toBeNull(); + }); + + it("should clear error notification on callback", () => { + render(); + fireEvent.keyDown(window, { key: "e", ctrlKey: true }); + fireEvent.click(screen.getByText("Close")); // Assuming 'Close' is the text inside the notification's close button + expect(screen.queryByText("select first")).not.toBeInTheDocument(); + }); +}); diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.stories.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.stories.tsx new file mode 100644 index 0000000000..d54aa50494 --- /dev/null +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.stories.tsx @@ -0,0 +1,9 @@ +import MenuOptionDesktopEditorOpenSelectionNoSelectWarning from "./menu-option-desktop-editor-open-selection-no-select-warning"; + +export default { + title: "components/molecules/menu-option-desktop-editor-open-selection-no-select-warning" +}; + +export const Default = () => { + return ; +}; diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.tsx index 5b639d91ea..436c327133 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.tsx @@ -1,28 +1,41 @@ -import { memo } from "react"; +import { memo, useState } from "react"; import useFetch from "../../../hooks/use-fetch"; import useHotKeys from "../../../hooks/use-keyboard/use-hotkeys"; import { IEnvFeatures } from "../../../interfaces/IEnvFeatures"; import { UrlQuery } from "../../../shared/url-query"; +import Notification, { NotificationType } from "../../atoms/notification/notification"; interface IMenuOptionDesktopEditorOpenSelectionNoSelectWarningProps { select: string[]; - isReadonly: boolean; + isReadOnly: boolean; } const MenuOptionDesktopEditorOpenSelectionNoSelectWarning: React.FunctionComponent = - memo(({ select, isReadonly }) => { + memo(({ select, isReadOnly }) => { // Check API to know if feature is needed! const featuresResult = useFetch(new UrlQuery().UrlApiFeaturesAppSettings(), "get"); const dataFeatures = featuresResult?.data as IEnvFeatures | undefined; + // for showing a notification + const [isError, setIsError] = useState(""); + /** * Open editor with keys - command + e */ useHotKeys({ key: "e", ctrlKeyOrMetaKey: true }, () => { - if (dataFeatures?.openEditorEnabled !== true || isReadonly || select.length >= 1) return; + if (dataFeatures?.openEditorEnabled !== true || isReadOnly || select.length >= 1) return; + setIsError("select first"); }); - return <>; + return ( + <> + {isError !== "" ? ( + setIsError("")} type={NotificationType.default}> + {isError} + + ) : null} + + ); }); export default MenuOptionDesktopEditorOpenSelectionNoSelectWarning; diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.spec.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.spec.tsx index 050c4b9a6f..d87f4c72cf 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.spec.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.spec.tsx @@ -1,5 +1,6 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import * as useFetch from "../../../hooks/use-fetch"; +import * as useHotKeys from "../../../hooks/use-keyboard/use-hotkeys"; import * as useLocation from "../../../hooks/use-location/use-location"; import { IArchiveProps } from "../../../interfaces/IArchiveProps"; import { IConnectionDefault } from "../../../interfaces/IConnectionDefault"; @@ -58,18 +59,61 @@ describe("ModalDesktopEditorOpenConfirmation", () => { ); }); + it("-- calls StartMenuOptionDesktopEditorOpenSelection on hotkey trigger", async () => { + const mockGetIConnectionDefaultFeatureToggle = { + statusCode: 200, + data: { + openEditorEnabled: false + } as IEnvFeatures + } as IConnectionDefault; + + const useHotkeysSpy = jest.spyOn(useHotKeys, "default").mockImplementationOnce(() => { + return { key: "e", ctrlKey: true }; + }); + + const useFetchSpy = jest + .spyOn(useFetch, "default") + .mockImplementationOnce(() => mockGetIConnectionDefaultFeatureToggle); + + const component = render( + {}} + /> + ); + + expect(useFetchSpy).toHaveBeenCalled(); + expect(useHotkeysSpy).toHaveBeenCalled(); + + component.unmount(); + }); + it("calls StartMenuOptionDesktopEditorOpenSelection on hotkey trigger", async () => { const mockIConnectionDefaultResolve: Promise = Promise.resolve({ data: true, statusCode: 200 } as IConnectionDefault); + const mockGetIConnectionDefaultFeatureToggle = { + statusCode: 200, + data: { + openEditorEnabled: true + } as IEnvFeatures + } as IConnectionDefault; + const fetchPostSpy = jest .spyOn(FetchPost, "default") .mockReset() .mockImplementationOnce(() => mockIConnectionDefaultResolve) .mockImplementationOnce(() => mockIConnectionDefaultResolve); + const useFetchSpy = jest + .spyOn(useFetch, "default") + .mockImplementationOnce(() => mockGetIConnectionDefaultFeatureToggle) + .mockImplementationOnce(() => mockGetIConnectionDefaultFeatureToggle); + const component = render( { fireEvent.keyDown(document.body, { key: "e", ctrlKey: true }); await waitFor(() => { + expect(useFetchSpy).toHaveBeenCalled(); expect(fetchPostSpy).toHaveBeenCalled(); expect(fetchPostSpy).toHaveBeenCalledTimes(2); expect(fetchPostSpy).toHaveBeenNthCalledWith( diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.tsx index 4a444ca8af..01c3b29891 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.tsx @@ -100,6 +100,9 @@ const MenuOptionDesktopEditorOpenSelection: React.FunctionComponent { + const isReadOnlyOrDisabled = !dataFeatures?.openEditorEnabled || isReadOnly; + console.log(`is ReadOnly/ or disabled: ${isReadOnlyOrDisabled}`); + StartMenuOptionDesktopEditorOpenSelection( select, isCollections, @@ -107,7 +110,7 @@ const MenuOptionDesktopEditorOpenSelection: React.FunctionComponent { // do nothing }); diff --git a/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx b/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx index 2c686ef9a4..a26dd37dd0 100644 --- a/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx @@ -14,6 +14,7 @@ import HamburgerMenuToggle from "../../atoms/hamburger-menu-toggle/hamburger-men import MenuOptionModal from "../../atoms/menu-option-modal/menu-option-modal"; import MoreMenu from "../../atoms/more-menu/more-menu"; import MenuSearchBar from "../../molecules/menu-inline-search/menu-inline-search"; +import MenuOptionDesktopEditorOpenSelectionNoSelectWarning from "../../molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning"; import MenuOptionDesktopEditorOpenSelection from "../../molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection"; import MenuOptionMoveFolderToTrash from "../../molecules/menu-option-move-folder-to-trash/menu-option-move-folder-to-trash"; import MenuOptionMoveToTrash from "../../molecules/menu-option-move-to-trash/menu-option-move-to-trash"; @@ -263,6 +264,11 @@ const MenuArchive: React.FunctionComponent = memo(() => { {/* onClick={() => allSelection()} */} + + {select.length >= 1 ? ( <> = memo(() => { /> ) : null} + = memo(() => { localization={localization.MessageDisplayOptions} testName="display-options" /> + = memo(() => { set={setIsSynchronizeManuallyOpen} localization={localization.MessageSynchronizeManually} /> + {state ? ( Date: Thu, 22 Feb 2024 23:56:18 +0100 Subject: [PATCH 099/125] changes --- ...on-desktop-editor-open-selection-no-select-warning.tsx | 4 +++- .../menu-option-desktop-editor-open-selection.spec.tsx | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.tsx index 436c327133..38ebc174c0 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.tsx @@ -23,7 +23,9 @@ const MenuOptionDesktopEditorOpenSelectionNoSelectWarning: React.FunctionCompone * Open editor with keys - command + e */ useHotKeys({ key: "e", ctrlKeyOrMetaKey: true }, () => { - if (dataFeatures?.openEditorEnabled !== true || isReadOnly || select.length >= 1) return; + if (dataFeatures?.openEditorEnabled !== true || isReadOnly || select.length >= 1) { + return; + } setIsError("select first"); }); diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.spec.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.spec.tsx index d87f4c72cf..0c2863b904 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.spec.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection/menu-option-desktop-editor-open-selection.spec.tsx @@ -67,14 +67,14 @@ describe("ModalDesktopEditorOpenConfirmation", () => { } as IEnvFeatures } as IConnectionDefault; - const useHotkeysSpy = jest.spyOn(useHotKeys, "default").mockImplementationOnce(() => { - return { key: "e", ctrlKey: true }; - }); - const useFetchSpy = jest .spyOn(useFetch, "default") .mockImplementationOnce(() => mockGetIConnectionDefaultFeatureToggle); + const useHotkeysSpy = jest.spyOn(useHotKeys, "default").mockImplementationOnce(() => { + return { key: "e", ctrlKey: true }; + }); + const component = render( Date: Fri, 23 Feb 2024 00:01:27 +0100 Subject: [PATCH 100/125] add nice warning --- ...-open-selection-no-select-warning.spec.tsx | 73 +++++++++++++++---- ...ditor-open-selection-no-select-warning.tsx | 16 +++- .../organisms/menu-archive/menu-archive.tsx | 10 +-- .../src/localization/localization.json | 4 + 4 files changed, 79 insertions(+), 24 deletions(-) diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.spec.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.spec.tsx index ce6174e005..e2084f1740 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.spec.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.spec.tsx @@ -1,4 +1,8 @@ -import { fireEvent, render, screen } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import * as useFetch from "../../../hooks/use-fetch"; +import { IConnectionDefault } from "../../../interfaces/IConnectionDefault"; +import { IEnvFeatures } from "../../../interfaces/IEnvFeatures"; +import * as Notification from "../../atoms/notification/notification"; import MenuOptionDesktopEditorOpenSelectionNoSelectWarning from "./menu-option-desktop-editor-open-selection-no-select-warning"; describe("MenuOptionDesktopEditorOpenSelectionNoSelectWarning", () => { @@ -7,38 +11,75 @@ describe("MenuOptionDesktopEditorOpenSelectionNoSelectWarning", () => { }); it("should show error notification when trying to open editor without selecting anything", () => { + const notificationSpy = jest.spyOn(Notification, "default").mockImplementationOnce((event) => { + return

{event.children}

; + }); + render(); + fireEvent.keyDown(window, { key: "e", ctrlKey: true }); - expect(screen.getByText("select first")).toBeTruthy(); + + waitFor(() => { + expect(notificationSpy).toHaveBeenCalledTimes(1); + + expect(screen.queryByTestId("notification-spy")).toBeTruthy(); + }); }); it("should not show error notification when select is not empty", () => { + const notificationSpy = jest.spyOn(Notification, "default").mockImplementationOnce((event) => { + return

{event.children}

; + }); + render( ); fireEvent.keyDown(window, { key: "e", ctrlKey: true }); - expect(screen.queryByText("select first")).toBeNull(); + expect(notificationSpy).toHaveBeenCalledTimes(0); + + expect(screen.queryByTestId("notification-spy")).toBeFalsy(); }); it("should not show error notification when read-only mode is enabled", () => { render(); + + const notificationSpy = jest.spyOn(Notification, "default").mockImplementationOnce((event) => { + return

{event.children}

; + }); + fireEvent.keyDown(window, { key: "e", ctrlKey: true }); - expect(screen.queryByText("select first")).toBeNull(); + expect(notificationSpy).toHaveBeenCalledTimes(0); + + expect(screen.queryByTestId("notification-spy")).toBeFalsy(); }); it("should not show error notification when editor feature is disabled", () => { - jest.spyOn(window, "fetch").mockImplementation(() => ({ - json: async () => ({ openEditorEnabled: false }) - })); - render(); - fireEvent.keyDown(window, { key: "e", ctrlKey: true }); - expect(screen.queryByText("select first")).toBeNull(); - }); + const mockGetIConnectionDefaultFeatureToggle = { + statusCode: 200, + data: { + openEditorEnabled: false + } as IEnvFeatures + } as IConnectionDefault; - it("should clear error notification on callback", () => { - render(); - fireEvent.keyDown(window, { key: "e", ctrlKey: true }); - fireEvent.click(screen.getByText("Close")); // Assuming 'Close' is the text inside the notification's close button - expect(screen.queryByText("select first")).not.toBeInTheDocument(); + const useFetchSpy = jest + .spyOn(useFetch, "default") + .mockImplementationOnce(() => mockGetIConnectionDefaultFeatureToggle) + .mockImplementationOnce(() => mockGetIConnectionDefaultFeatureToggle); + + const notificationSpy = jest.spyOn(Notification, "default").mockImplementationOnce((event) => { + return

{event.children}

; + }); + + const component = render( + + ); + + fireEvent.keyDown(component.container, { key: "e", ctrlKey: true }); + + expect(screen.queryByTestId("notification-spy")).toBeFalsy(); + + expect(notificationSpy).toHaveBeenCalledTimes(0); + + expect(useFetchSpy).toHaveBeenCalled(); }); }); diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.tsx index 38ebc174c0..77939f5f84 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-selection-no-select-warning/menu-option-desktop-editor-open-selection-no-select-warning.tsx @@ -1,17 +1,21 @@ import { memo, useState } from "react"; import useFetch from "../../../hooks/use-fetch"; +import useGlobalSettings from "../../../hooks/use-global-settings"; import useHotKeys from "../../../hooks/use-keyboard/use-hotkeys"; import { IEnvFeatures } from "../../../interfaces/IEnvFeatures"; +import localization from "../../../localization/localization.json"; +import { Language } from "../../../shared/language"; import { UrlQuery } from "../../../shared/url-query"; import Notification, { NotificationType } from "../../atoms/notification/notification"; interface IMenuOptionDesktopEditorOpenSelectionNoSelectWarningProps { - select: string[]; + select?: string[]; isReadOnly: boolean; } const MenuOptionDesktopEditorOpenSelectionNoSelectWarning: React.FunctionComponent = memo(({ select, isReadOnly }) => { + const selectArray = select ?? []; // Check API to know if feature is needed! const featuresResult = useFetch(new UrlQuery().UrlApiFeaturesAppSettings(), "get"); const dataFeatures = featuresResult?.data as IEnvFeatures | undefined; @@ -19,14 +23,20 @@ const MenuOptionDesktopEditorOpenSelectionNoSelectWarning: React.FunctionCompone // for showing a notification const [isError, setIsError] = useState(""); + // Get language keys + const settings = useGlobalSettings(); + const language = new Language(settings.language); + const MessageItemSelectionRequired = language.key(localization.MessageItemSelectionRequired); + /** * Open editor with keys - command + e */ useHotKeys({ key: "e", ctrlKeyOrMetaKey: true }, () => { - if (dataFeatures?.openEditorEnabled !== true || isReadOnly || select.length >= 1) { + if (dataFeatures?.openEditorEnabled !== true || isReadOnly || selectArray.length >= 1) { + setIsError(""); return; } - setIsError("select first"); + setIsError(MessageItemSelectionRequired); }); return ( diff --git a/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx b/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx index a26dd37dd0..d7bca282d4 100644 --- a/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx @@ -172,6 +172,11 @@ const MenuArchive: React.FunctionComponent = memo(() => { + + {!select ? ( + + ); + }); + + const useHotKeysSpy = jest + .spyOn(useHotKeys, "default") + .mockReset() + .mockImplementationOnce((event, callback) => { + if (event?.key == "e" && callback) { + callback({} as KeyboardEvent); + } + }); + + const settings = useGlobalSettings(); + const language = new Language(settings.language); + const MessageItemSelectionRequired = language.key(localization.MessageItemSelectionRequired); + + const component = render( + + ); + + fireEvent.keyDown(component.container, { key: "e", ctrlKey: true }); + + expect(screen.queryByTestId("notification-spy")).toBeTruthy(); + + const errorMessage = screen.queryByTestId("notification-spy")?.innerHTML; + + expect(errorMessage).toBe(MessageItemSelectionRequired); + + expect(screen.queryByTestId("notification-spy")?.innerHTML).toBeTruthy(); + + expect(notificationSpy).toHaveBeenCalledTimes(1); + expect(useHotKeysSpy).toHaveBeenCalledTimes(2); + + expect(useFetchSpy).toHaveBeenCalled(); + + fireEvent.click(screen.getByTestId("notification-spy-button")); + + const errorMessage2 = screen.queryByTestId("notification-spy")?.innerHTML; + + expect(errorMessage2).toBe(undefined); + }); }); diff --git a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.spec.tsx b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.spec.tsx index c1479096e9..5887f2578f 100644 --- a/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.spec.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/menu-option-desktop-editor-open-single/menu-option-desktop-editor-open-single.spec.tsx @@ -1,8 +1,11 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import * as useFetch from "../../../hooks/use-fetch"; +import useGlobalSettings from "../../../hooks/use-global-settings"; import { IConnectionDefault } from "../../../interfaces/IConnectionDefault"; import { IEnvFeatures } from "../../../interfaces/IEnvFeatures"; +import localization from "../../../localization/localization.json"; import * as FetchPost from "../../../shared/fetch/fetch-post"; +import { Language } from "../../../shared/language"; import { UrlQuery } from "../../../shared/url-query"; import * as Notification from "../../atoms/notification/notification"; import MenuOptionDesktopEditorOpenSingle, { @@ -161,6 +164,86 @@ describe("MenuOptionDesktopEditorOpenSingle", () => { const collections = true; const isReadOnly = false; + // Get language keys + const settings = useGlobalSettings(); + const language = new Language(settings.language); + const MessageDesktopEditorUnableToOpen = language.key( + localization.MessageDesktopEditorUnableToOpen + ); + + const container = render( + + ); + + expect(useFetchSpy).toHaveBeenCalled(); + + fireEvent.click(screen.getByTestId("menu-option-desktop-editor-open-single")); + + expect(fetchPostSpy).toHaveBeenCalled(); + expect(fetchPostSpy).toHaveBeenNthCalledWith( + 1, + new UrlQuery().UrlApiDesktopEditorOpen(), + "f=%2Ftest.jpg&collections=true" + ); + + await waitFor(() => { + expect(notificationSpy).toHaveBeenCalled(); + expect(notificationSpy).toHaveBeenCalledWith( + { + callback: expect.anything(), + children: MessageDesktopEditorUnableToOpen, + type: "danger" + }, + {} + ); + }); + container.unmount(); + }); + + it("should hide feature toggle - set Error - click close", async () => { + const mockGetIConnectionDefaultFeatureToggle = { + statusCode: 200, + data: { + openEditorEnabled: true + } as IEnvFeatures + } as IConnectionDefault; + + const useFetchSpy = jest + .spyOn(useFetch, "default") + .mockReset() + .mockImplementationOnce(() => mockGetIConnectionDefaultFeatureToggle); + + const notificationSpy = jest.spyOn(Notification, "default").mockImplementationOnce((event) => ( + + )); + + const mockIConnectionDefaultResolve: Promise = Promise.resolve({ + data: null, + statusCode: 500 + } as IConnectionDefault); + + const fetchPostSpy = jest + .spyOn(FetchPost, "default") + .mockReset() + .mockImplementationOnce(() => mockIConnectionDefaultResolve); + + const subPath = "/test.jpg"; + const collections = true; + const isReadOnly = false; + + // Get language keys + const settings = useGlobalSettings(); + const language = new Language(settings.language); + const MessageDesktopEditorUnableToOpen = language.key( + localization.MessageDesktopEditorUnableToOpen + ); + const container = render( { await waitFor(() => { expect(notificationSpy).toHaveBeenCalled(); + expect(notificationSpy).toHaveBeenCalledWith( + { + callback: expect.anything(), + children: MessageDesktopEditorUnableToOpen, + type: "danger" + }, + {} + ); + + const errorMessage1 = screen.queryByTestId("notification-spy-button")?.innerHTML; + expect(errorMessage1).toBe(MessageDesktopEditorUnableToOpen); + + fireEvent.click(screen.getByTestId("notification-spy-button")); + + const errorMessage2 = screen.queryByTestId("notification-spy-button")?.innerHTML; + + expect(errorMessage2).toBe(undefined); }); container.unmount(); }); From b8f833034ec283144861ffdc01b97680dc6b08ed Mon Sep 17 00:00:00 2001 From: Dion Date: Fri, 23 Feb 2024 00:05:57 +0100 Subject: [PATCH 103/125] update --- starskydesktop/src/app/menu/app-menu.ts | 76 +++++++++++++------------ 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/starskydesktop/src/app/menu/app-menu.ts b/starskydesktop/src/app/menu/app-menu.ts index dd2c14ee88..18becb8d14 100644 --- a/starskydesktop/src/app/menu/app-menu.ts +++ b/starskydesktop/src/app/menu/app-menu.ts @@ -1,5 +1,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import { app, BrowserWindow, Menu, shell } from "electron"; +import { + app, BrowserWindow, Menu, shell +} from "electron"; import { EditFile } from "../edit-file/edit-file"; import { IsDutch } from "../i18n/i18n"; import createMainWindow from "../main-window/create-main-window"; @@ -32,36 +34,36 @@ function AppMenu() { const menu = Menu.buildFromTemplate([ ...(isMac ? [ - { - label: app.name, - submenu: [ - { - label: IsDutch() ? "Over Starsky" : "About Starsky", - role: "about", - }, - { type: "separator" }, - { role: "services" }, - { type: "separator" }, - { - label: IsDutch() ? "Verberg Starsky" : "Hide Starsky", - role: "hide", - }, - { - label: IsDutch() ? "Verberg andere" : "Hide Others", - role: "hideothers", - }, - { - label: IsDutch() ? "Toon alles" : "Show All", - role: "unhide", - }, - { type: "separator" }, - { - label: IsDutch() ? "Starsky afsluiten" : "Quit Starsky", - role: "quit", - }, - ] as any, - }, - ] + { + label: app.name, + submenu: [ + { + label: IsDutch() ? "Over Starsky" : "About Starsky", + role: "about", + }, + { type: "separator" }, + { role: "services" }, + { type: "separator" }, + { + label: IsDutch() ? "Verberg Starsky" : "Hide Starsky", + role: "hide", + }, + { + label: IsDutch() ? "Verberg andere" : "Hide Others", + role: "hideothers", + }, + { + label: IsDutch() ? "Toon alles" : "Show All", + role: "unhide", + }, + { type: "separator" }, + { + label: IsDutch() ? "Starsky afsluiten" : "Quit Starsky", + role: "quit", + }, + ] as any, + }, + ] : []), { label: IsDutch() ? "Bestand" : "File", @@ -93,13 +95,13 @@ function AppMenu() { isMac ? { - label: IsDutch() ? "Venster sluiten" : "Close Window", - role: "close", - } + label: IsDutch() ? "Venster sluiten" : "Close Window", + role: "close", + } : { - label: IsDutch() ? "App sluiten" : "Close App", - role: "quit", - }, + label: IsDutch() ? "App sluiten" : "Close App", + role: "quit", + }, ], }, { From ca37ef22e426fbadb4e4d73778a240f928fd9620 Mon Sep 17 00:00:00 2001 From: Dion Date: Fri, 23 Feb 2024 19:26:29 +0100 Subject: [PATCH 104/125] split app settings in sperate module --- .../preference-app-settings-desktop.tsx | 12 ++ ...nces-app-settings-storage-folder.spec.tsx} | 127 ++---------------- ...es-app-settings-storage-folder.stories.tsx | 11 ++ ...references-app-settings-storage-folder.tsx | 101 ++++++++++++++ .../preferences-app-settings.tsx | 109 +-------------- .../clientapp/src/interfaces/IAppSettings.ts | 5 + .../IAppSettingsDefaultEditorApplication.ts | 6 + .../src/interfaces/ICollectionsOpenType.ts | 5 + .../src/localization/localization.json | 20 +++ 9 files changed, 174 insertions(+), 222 deletions(-) create mode 100644 starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx rename starsky/starsky/clientapp/src/components/organisms/{preferences-app-settings/preferences-app-settings.spec.tsx => preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.spec.tsx} (58%) create mode 100644 starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.stories.tsx create mode 100644 starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.tsx create mode 100644 starsky/starsky/clientapp/src/interfaces/IAppSettingsDefaultEditorApplication.ts create mode 100644 starsky/starsky/clientapp/src/interfaces/ICollectionsOpenType.ts diff --git a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx new file mode 100644 index 0000000000..f9f9e913be --- /dev/null +++ b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx @@ -0,0 +1,12 @@ +import React from "react"; + +const PreferencesAppSettingsDesktop: React.FunctionComponent = () => { + return ( + <> +

Desktop

+ Only used when using Starsky as desktop + + ); +}; + +export default PreferencesAppSettingsDesktop; diff --git a/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings/preferences-app-settings.spec.tsx b/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.spec.tsx similarity index 58% rename from starsky/starsky/clientapp/src/components/organisms/preferences-app-settings/preferences-app-settings.spec.tsx rename to starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.spec.tsx index 3ebb72c1da..616345d77f 100644 --- a/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings/preferences-app-settings.spec.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.spec.tsx @@ -1,62 +1,18 @@ -import { act, createEvent, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { act, createEvent, fireEvent, render, screen } from "@testing-library/react"; import * as useFetch from "../../../hooks/use-fetch"; import { IConnectionDefault, newIConnectionDefault } from "../../../interfaces/IConnectionDefault"; import * as FetchPost from "../../../shared/fetch/fetch-post"; import { UrlQuery } from "../../../shared/url-query"; -import PreferencesAppSettings, { ChangeSetting } from "./preferences-app-settings"; +import PreferencesAppSettingsStorageFolder, { + ChangeSetting +} from "./preferences-app-settings-storage-folder"; describe("PreferencesAppSettings", () => { it("renders", () => { - render(); + render(); }); describe("context", () => { - it("disabled by default", () => { - // usage ==> import * as useFetch from '../../../hooks/use-fetch'; - jest - .spyOn(useFetch, "default") - .mockImplementationOnce(() => newIConnectionDefault()) - .mockImplementationOnce(() => newIConnectionDefault()); - - const component = render(); - - const switchButtons = screen.queryAllByTestId("switch-button-right"); - - const verbose = switchButtons.find( - (p) => p.getAttribute("name") === "verbose" - ) as HTMLInputElement; - - expect(verbose.disabled).toBeTruthy(); - - component.unmount(); - }); - - it("not disabled when admin", () => { - const connectionDefault = { - statusCode: 200, - data: ["AppSettingsWrite"] - } as IConnectionDefault; - // usage ==> import * as useFetch from '../../../hooks/use-fetch'; - jest - .spyOn(useFetch, "default") - .mockImplementationOnce(() => connectionDefault) - .mockImplementationOnce(() => connectionDefault) - .mockImplementationOnce(() => connectionDefault) - .mockImplementationOnce(() => connectionDefault); - - const component = render(); - - const switchButtons = screen.queryAllByTestId("switch-button-right"); - - const verbose = switchButtons.find( - (p) => p.getAttribute("name") === "verbose" - ) as HTMLInputElement; - - expect(verbose.disabled).toBeFalsy(); - - component.unmount(); - }); - it("filled right data", () => { const permissions = { statusCode: 200, @@ -78,7 +34,7 @@ describe("PreferencesAppSettings", () => { .mockImplementationOnce(() => permissions) .mockImplementationOnce(() => appSettings); - const component = render(); + const component = render(); const formControls = screen.queryAllByTestId("form-control"); @@ -124,7 +80,7 @@ describe("PreferencesAppSettings", () => { .spyOn(FetchPost, "default") .mockImplementationOnce(() => mockIConnectionDefault); - const component = render(); + const component = render(); const formControls = screen .queryAllByTestId("form-control") @@ -181,7 +137,7 @@ describe("PreferencesAppSettings", () => { .spyOn(FetchPost, "default") .mockImplementationOnce(() => mockIConnectionDefault); - const component = render(); + const component = render(); const formControls = screen .queryAllByTestId("form-control") @@ -210,76 +166,9 @@ describe("PreferencesAppSettings", () => { component.unmount(); }); }); - - it("toggle verbose", async () => { - const permissions = { - statusCode: 200, - data: ["AppSettingsWrite"] - } as IConnectionDefault; - const appSettings = { - statusCode: 200, - data: { - verbose: true, - storageFolder: "test" - } - } as IConnectionDefault; - - // usage ==> import * as useFetch from '../../../hooks/use-fetch'; - jest - .spyOn(useFetch, "default") - .mockReset() - .mockImplementationOnce(() => permissions) - .mockImplementationOnce(() => appSettings) - .mockImplementationOnce(() => permissions) - .mockImplementationOnce(() => appSettings) - .mockImplementationOnce(() => permissions) - .mockImplementationOnce(() => appSettings) - .mockImplementationOnce(() => permissions) - .mockImplementationOnce(() => appSettings) - .mockImplementationOnce(() => permissions) - .mockImplementationOnce(() => appSettings); - - const component = render(); - - const fetchPostSpy = jest - .spyOn(FetchPost, "default") - .mockReset() - .mockImplementationOnce(() => { - return Promise.resolve({ - statusCode: 400, - data: null - }); - }); - - const switchButtons = screen.queryAllByTestId("switch-button-right"); - - const verbose = switchButtons.find( - (p) => p.getAttribute("name") === "verbose" - ) as HTMLElement; - - verbose.click(); - - await waitFor(() => expect(fetchPostSpy).toHaveBeenCalled()); - - component.unmount(); - }); }); describe("ChangeSetting", () => { - it("should set value with empty string as name when name is not provided", async () => { - const value = "test value"; - const fetchPostSpy = jest.spyOn(FetchPost, "default").mockImplementationOnce(() => { - return Promise.resolve({ - statusCode: 200, - data: null - }); - }); - - const statusCode = await ChangeSetting(value); - expect(statusCode).toBe(200); - expect(fetchPostSpy).toHaveBeenCalled(); - }); - it("should set value with provided name when name is provided", async () => { const value = "test value"; const name = "test name"; diff --git a/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.stories.tsx b/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.stories.tsx new file mode 100644 index 0000000000..aa67399455 --- /dev/null +++ b/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.stories.tsx @@ -0,0 +1,11 @@ +import PreferencesAppSettingsStorageFolder from "./preferences-app-settings-storage-folder"; + +export default { + title: "components/organisms/preferences-app-settings-storage-folder" +}; + +export const Default = () => { + return ; +}; + +Default.storyName = "default"; diff --git a/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.tsx b/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.tsx new file mode 100644 index 0000000000..0130677f12 --- /dev/null +++ b/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.tsx @@ -0,0 +1,101 @@ +import React, { useEffect, useState } from "react"; +import useFetch from "../../../hooks/use-fetch"; +import useGlobalSettings from "../../../hooks/use-global-settings"; +import { IAppSettings } from "../../../interfaces/IAppSettings"; +import localization from "../../../localization/localization.json"; +import FetchPost from "../../../shared/fetch/fetch-post"; +import { Language } from "../../../shared/language"; +import { UrlQuery } from "../../../shared/url-query"; +import FormControl from "../../atoms/form-control/form-control"; +export async function ChangeSetting(value: string, name?: string): Promise { + const bodyParams = new URLSearchParams(); + bodyParams.set(name ?? "", value); + const result = await FetchPost(new UrlQuery().UrlApiAppSettings(), bodyParams.toString()); + return result?.statusCode; +} + +const PreferencesAppSettingsStorageFolder: React.FunctionComponent = () => { + const settings = useGlobalSettings(); + const language = new Language(settings.language); + const MessageAppSettingsEntireAppScope = language.key( + localization.MessageAppSettingsEntireAppScope + ); + const MessageChangeNeedReSync = language.key(localization.MessageChangeNeedReSync); + const MessageAppSettingsStorageFolder = language.key( + localization.MessageAppSettingsStorageFolder + ); + const MessageAppSettingStorageFolderSaveFail = language.key( + localization.MessageAppSettingStorageFolderSaveFail + ); + const MessageAppSettingStorageFolderEnvUsedFail = language.key( + localization.MessageAppSettingStorageFolderEnvUsedFail + ); + + const permissionsData = useFetch(new UrlQuery().UrlAccountPermissions(), "get"); + + const [isEnabled, setIsEnabled] = useState(false); + + useEffect(() => { + function permissions(): boolean { + if (!permissionsData?.data?.includes || permissionsData?.statusCode !== 200) { + return false; + } + return permissionsData.data.includes("AppSettingsWrite"); + } + + setIsEnabled(permissions()); + }, [permissionsData]); + + const [storageFolderNotFound, setStorageFolderNotFound] = useState(false); + + const appSettings = useFetch(new UrlQuery().UrlApiAppSettings(), "get") + ?.data as IAppSettings | null; + + const [storageFolder, setStorageFolder] = useState(appSettings?.storageFolder); + + // const [defaultDesktopEditor, setDefaultDesktopEditor] = useState( + // appSettings?.defaultDesktopEditor + // ); + + useEffect(() => { + setStorageFolder(appSettings?.storageFolder); + }, [appSettings]); + + return ( + <> +
{MessageAppSettingsEntireAppScope}
+

{MessageAppSettingsStorageFolder}

+ { + const resultStatusCode = await ChangeSetting(e.target.innerText, "storageFolder"); + setStorageFolder(e.target.innerText); + setStorageFolderNotFound(resultStatusCode === 404); + }} + contentEditable={isEnabled && appSettings?.storageFolderAllowEdit === true} + > + {storageFolder} + + + {storageFolderNotFound ? ( +
+ {MessageAppSettingStorageFolderSaveFail} +
+ ) : null} + + {storageFolder !== appSettings?.storageFolder && !storageFolderNotFound ? ( +
+ {MessageChangeNeedReSync} +
+ ) : null} + + {appSettings?.storageFolderAllowEdit !== true ? ( +
+ {MessageAppSettingStorageFolderEnvUsedFail} +
+ ) : null} + + ); +}; + +export default PreferencesAppSettingsStorageFolder; diff --git a/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings/preferences-app-settings.tsx b/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings/preferences-app-settings.tsx index 6e2757021e..428a5b2c4f 100644 --- a/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings/preferences-app-settings.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings/preferences-app-settings.tsx @@ -1,111 +1,14 @@ -import React, { useEffect, useState } from "react"; -import useFetch from "../../../hooks/use-fetch"; -import useGlobalSettings from "../../../hooks/use-global-settings"; -import { IAppSettings } from "../../../interfaces/IAppSettings"; -import FetchPost from "../../../shared/fetch/fetch-post"; -import { Language } from "../../../shared/language"; -import { UrlQuery } from "../../../shared/url-query"; -import FormControl from "../../atoms/form-control/form-control"; -import SwitchButton from "../../atoms/switch-button/switch-button"; - -export async function ChangeSetting(value: string, name?: string): Promise { - const bodyParams = new URLSearchParams(); - bodyParams.set(name ?? "", value); - const result = await FetchPost(new UrlQuery().UrlApiAppSettings(), bodyParams.toString()); - return result?.statusCode; -} - -const PreferencesAppSettings: React.FunctionComponent = () => { - const settings = useGlobalSettings(); - const language = new Language(settings.language); - const MessageAppSettingsEntireAppScope = language.text( - "De AppSettings mogen alleen worden aangepast door Administrators. " + - "Deze instellingen worden toegepast voor de gehele applicatie ", - "The AppSettings may only be modified by Administrators. These settings are applied for the entire application" - ); - const MessageChangeNeedReSync = language.text( - "Je hebt deze instelling veranderd, nu dien je een volledige sync uit te voeren. Ga naar de hoofdmap, het meer-menu en klik op handmatig synchroniseren.", - "You have changed this setting, now you need to perform a full sync. Go to the root folder, the more menu and click on manual sync." - ); - - const permissionsData = useFetch(new UrlQuery().UrlAccountPermissions(), "get"); - - const [isEnabled, setIsEnabled] = useState(false); - - useEffect(() => { - function permissions(): boolean { - if (!permissionsData?.data?.includes || permissionsData?.statusCode !== 200) { - return false; - } - return permissionsData.data.includes("AppSettingsWrite"); - } - - setIsEnabled(permissions()); - }, [permissionsData]); - - const [storageFolderNotFound, setStorageFolderNotFound] = useState(false); - - const appSettings = useFetch(new UrlQuery().UrlApiAppSettings(), "get") - ?.data as IAppSettings | null; - - const [isVerbose, setIsVerbose] = useState(appSettings?.verbose); - const [storageFolder, setStorageFolder] = useState(appSettings?.storageFolder); - - useEffect(() => { - setIsVerbose(appSettings?.verbose); - setStorageFolder(appSettings?.storageFolder); - }, [appSettings]); +import React from "react"; +import PreferencesAppSettingsDesktop from "../preference-app-settings-desktop/preference-app-settings-desktop"; +import PreferencesAppSettingsStorageFolder from "../preferences-app-settings-storage-folder/preferences-app-settings-storage-folder"; +const PreferencesAppSettings: React.FunctionComponent = () => { return (
AppSettings
-
{MessageAppSettingsEntireAppScope}
- -

Verbose logging

- - { - ChangeSetting((!toggle).toString(), name); - setIsVerbose(!toggle); - }} - leftLabel={"on"} - name="verbose" - rightLabel={"off"} - /> - -

Storage Folder

- { - const resultStatusCode = await ChangeSetting(e.target.innerText, "storageFolder"); - setStorageFolder(e.target.innerText); - setStorageFolderNotFound(resultStatusCode === 404); - }} - contentEditable={isEnabled && appSettings?.storageFolderAllowEdit === true} - > - {storageFolder} - - - {storageFolderNotFound ? ( -
- Directory not found so not saved -
- ) : null} - - {storageFolder !== appSettings?.storageFolder && !storageFolderNotFound ? ( -
- {MessageChangeNeedReSync} -
- ) : null} - - {appSettings?.storageFolderAllowEdit !== true ? ( -
- You should update the Environment variable app__storageFolder -
- ) : null} + +
); diff --git a/starsky/starsky/clientapp/src/interfaces/IAppSettings.ts b/starsky/starsky/clientapp/src/interfaces/IAppSettings.ts index 7664542127..cda5da48f5 100644 --- a/starsky/starsky/clientapp/src/interfaces/IAppSettings.ts +++ b/starsky/starsky/clientapp/src/interfaces/IAppSettings.ts @@ -1,5 +1,10 @@ +import { IAppSettingsDefaultEditorApplication } from "./IAppSettingsDefaultEditorApplication"; +import { RawJpegMode } from "./ICollectionsOpenType"; + export interface IAppSettings { verbose: boolean; storageFolder: string; storageFolderAllowEdit: boolean; + defaultDesktopEditor: IAppSettingsDefaultEditorApplication[]; + desktopCollectionsOpen: RawJpegMode; } diff --git a/starsky/starsky/clientapp/src/interfaces/IAppSettingsDefaultEditorApplication.ts b/starsky/starsky/clientapp/src/interfaces/IAppSettingsDefaultEditorApplication.ts new file mode 100644 index 0000000000..b7ca323e34 --- /dev/null +++ b/starsky/starsky/clientapp/src/interfaces/IAppSettingsDefaultEditorApplication.ts @@ -0,0 +1,6 @@ +import { ImageFormat } from "./IFileIndexItem"; + +export interface IAppSettingsDefaultEditorApplication { + applicationPath: string; + imageFormats: ImageFormat[]; +} diff --git a/starsky/starsky/clientapp/src/interfaces/ICollectionsOpenType.ts b/starsky/starsky/clientapp/src/interfaces/ICollectionsOpenType.ts new file mode 100644 index 0000000000..c50afb392d --- /dev/null +++ b/starsky/starsky/clientapp/src/interfaces/ICollectionsOpenType.ts @@ -0,0 +1,5 @@ +export enum RawJpegMode { + Default = 0, + Jpeg = 1, + Raw = 2 +} diff --git a/starsky/starsky/clientapp/src/localization/localization.json b/starsky/starsky/clientapp/src/localization/localization.json index 82a9e84e48..10f33cf345 100644 --- a/starsky/starsky/clientapp/src/localization/localization.json +++ b/starsky/starsky/clientapp/src/localization/localization.json @@ -263,6 +263,26 @@ "en": "Please select at least one item before proceeding.", "nl": "Selecteer alstublieft eerst minstens één item voordat u doorgaat." }, + "MessageAppSettingsEntireAppScope": { + "en": "The AppSettings may only be modified by Administrators. These settings are applied for the entire application.", + "nl": "De AppSettings mogen alleen worden aangepast door Administrators. Deze instellingen worden toegepast voor de gehele applicatie." + }, + "MessageChangeNeedReSync": { + "en": "You have changed this setting, now you need to perform a full sync. Go to the root folder, the more menu and click on manual sync.", + "nl": "Je hebt deze instelling veranderd, nu dien je een volledige sync uit te voeren. Ga naar de hoofdmap, het meer-menu en klik op handmatig synchroniseren." + }, + "MessageAppSettingsStorageFolder": { + "en": "Storage Folder", + "nl": "Opslag map" + }, + "MessageAppSettingStorageFolderSaveFail": { + "en": "Apologies, unable to save, we couldn't find the directory", + "nl": "Sorry het opslaan van de map is niet gelukt, de map is niet gevonden" + }, + "MessageAppSettingStorageFolderEnvUsedFail": { + "en": "Please update the 'app__storageFolder' environment variable to change the location", + "nl": "Update alsjeblieft de 'app__storageFolder' environment variable om de opslag locatie te veranderen" + }, "temp1": { "en": "", "nl": "" From 43f9c6e7913fe2426396807a4417631570e0ba65 Mon Sep 17 00:00:00 2001 From: Dion Date: Sat, 24 Feb 2024 19:38:17 +0100 Subject: [PATCH 105/125] add UI code --- .../Models/AppSettingsTransferObject.cs | 9 +++ .../Controllers/AppSettingsController.cs | 2 + .../preference-app-settings-desktop.tsx | 64 ++++++++++++++++++- ...references-app-settings-storage-folder.tsx | 11 ++-- .../src/localization/localization.json | 9 +++ .../Services/UpdateAppSettingsByPathTest.cs | 31 ++++++++- 6 files changed, 119 insertions(+), 7 deletions(-) diff --git a/starsky/starsky.foundation.platform/Models/AppSettingsTransferObject.cs b/starsky/starsky.foundation.platform/Models/AppSettingsTransferObject.cs index 970419ffd3..cc2fcc061d 100644 --- a/starsky/starsky.foundation.platform/Models/AppSettingsTransferObject.cs +++ b/starsky/starsky.foundation.platform/Models/AppSettingsTransferObject.cs @@ -1,3 +1,6 @@ +using System.Collections.Generic; +using starsky.foundation.platform.Enums; + namespace starsky.foundation.platform.Models { /// @@ -11,5 +14,11 @@ public sealed class AppSettingsTransferObject public bool? UseSystemTrash { get; set; } public bool? UseLocalDesktop { get; set; } + + public List DefaultDesktopEditor { get; set; } = []; + + public CollectionsOpenType.RawJpegMode DesktopCollectionsOpen { get; set; } = + CollectionsOpenType.RawJpegMode.Default; + } } diff --git a/starsky/starsky/Controllers/AppSettingsController.cs b/starsky/starsky/Controllers/AppSettingsController.cs index 783f8c4b03..ad18e0b25b 100644 --- a/starsky/starsky/Controllers/AppSettingsController.cs +++ b/starsky/starsky/Controllers/AppSettingsController.cs @@ -38,6 +38,8 @@ public AppSettingsController(AppSettings appSettings, public IActionResult Env() { var appSettings = _appSettings.CloneToDisplay(); + + // For end-to-end testing if ( Request != null! && Request.Headers.Any(p => p.Key == "x-force-html") ) { Response.Headers.ContentType = "text/html; charset=utf-8"; diff --git a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx index f9f9e913be..c08358091a 100644 --- a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx @@ -1,10 +1,72 @@ -import React from "react"; +import React, { useState } from "react"; +import useFetch from "../../../hooks/use-fetch"; +import useGlobalSettings from "../../../hooks/use-global-settings"; +import { IAppSettings } from "../../../interfaces/IAppSettings"; +import { RawJpegMode } from "../../../interfaces/ICollectionsOpenType"; +import localization from "../../../localization/localization.json"; +import FetchPost from "../../../shared/fetch/fetch-post"; +import { Language } from "../../../shared/language"; +import { UrlQuery } from "../../../shared/url-query"; +import FormControl from "../../atoms/form-control/form-control"; +import SwitchButton from "../../atoms/switch-button/switch-button"; const PreferencesAppSettingsDesktop: React.FunctionComponent = () => { + const appSettings = useFetch(new UrlQuery().UrlApiAppSettings(), "get") + ?.data as IAppSettings | null; + + const settings = useGlobalSettings(); + + const language = new Language(settings.language); + const MessageSwitchButtonDesktopCollectionsRawOn = language.key( + localization.MessageSwitchButtonDesktopCollectionsRawOn + ); + const MessageSwitchButtonDesktopCollectionsJpegDefaultOff = language.key( + localization.MessageSwitchButtonDesktopCollectionsJpegDefaultOff + ); + + // appSettings?.desktopCollectionsOpen + // List DefaultDesktopEditor + // CollectionsOpenType.RawJpegMode DesktopCollectionsOpen + + async function toggleCollections(value: boolean) { + const desktopCollectionsOpen = value ? RawJpegMode.Raw : RawJpegMode.Jpeg; + + const bodyParams = new URLSearchParams(); + bodyParams.set("desktopCollectionsOpen", desktopCollectionsOpen.toString()); + + const result = await FetchPost(new UrlQuery().UrlApiAppSettings(), bodyParams.toString()); + if (result.statusCode != 200) { + setIsMessage("FAIL"); + return; + } + setIsMessage("OK"); + } + + // for showing a notification + const [isMessage, setIsMessage] = useState(""); + return ( <>

Desktop

Only used when using Starsky as desktop + {isMessage !== "" ? ( +
{isMessage}
+ ) : null} + +

Tags:

+ ); }; diff --git a/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.tsx b/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.tsx index 0130677f12..17d8702a5f 100644 --- a/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.tsx @@ -7,6 +7,13 @@ import FetchPost from "../../../shared/fetch/fetch-post"; import { Language } from "../../../shared/language"; import { UrlQuery } from "../../../shared/url-query"; import FormControl from "../../atoms/form-control/form-control"; + +/** + * Update Change Settings + * @param value - content + * @param name - key name + * @returns void + */ export async function ChangeSetting(value: string, name?: string): Promise { const bodyParams = new URLSearchParams(); bodyParams.set(name ?? "", value); @@ -53,10 +60,6 @@ const PreferencesAppSettingsStorageFolder: React.FunctionComponent = () => { const [storageFolder, setStorageFolder] = useState(appSettings?.storageFolder); - // const [defaultDesktopEditor, setDefaultDesktopEditor] = useState( - // appSettings?.defaultDesktopEditor - // ); - useEffect(() => { setStorageFolder(appSettings?.storageFolder); }, [appSettings]); diff --git a/starsky/starsky/clientapp/src/localization/localization.json b/starsky/starsky/clientapp/src/localization/localization.json index 10f33cf345..d7a2728441 100644 --- a/starsky/starsky/clientapp/src/localization/localization.json +++ b/starsky/starsky/clientapp/src/localization/localization.json @@ -283,6 +283,15 @@ "en": "Please update the 'app__storageFolder' environment variable to change the location", "nl": "Update alsjeblieft de 'app__storageFolder' environment variable om de opslag locatie te veranderen" }, + "MessageSwitchButtonDesktopCollectionsRawOn": { + "en": "Raw first", + "nl": "Raw eerst" + }, + "MessageSwitchButtonDesktopCollectionsJpegDefaultOff": { + "en": "Jpeg first", + "nl": "Jpeg eerst" + }, + "temp1": { "en": "", "nl": "" diff --git a/starsky/starskytest/starsky.feature.settings/Services/UpdateAppSettingsByPathTest.cs b/starsky/starskytest/starsky.feature.settings/Services/UpdateAppSettingsByPathTest.cs index 10ca323d82..41f0ccecbf 100644 --- a/starsky/starskytest/starsky.feature.settings/Services/UpdateAppSettingsByPathTest.cs +++ b/starsky/starskytest/starsky.feature.settings/Services/UpdateAppSettingsByPathTest.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.feature.settings.Services; +using starsky.foundation.platform.Enums; using starsky.foundation.platform.JsonConverter; using starsky.foundation.platform.Models; using starsky.foundation.storage.Helpers; @@ -31,7 +32,8 @@ public async Task UpdateAppSettingsAsync_ValidInput_Success() new UpdateAppSettingsByPath(new AppSettings(), selectorStorage); var appSettingTransferObject = new AppSettingsTransferObject { - StorageFolder = testFolderPath, Verbose = true + StorageFolder = testFolderPath, + Verbose = true }; // Act @@ -62,7 +64,9 @@ public async Task UpdateAppSettingsAsync_ValidInput_Success_CompareJson() var updateAppSettingsByPath = new UpdateAppSettingsByPath(appSettings, selectorStorage); var appSettingTransferObject = new AppSettingsTransferObject { - StorageFolder = testFolderPath, Verbose = true, UseLocalDesktop = null + StorageFolder = testFolderPath, + Verbose = true, + UseLocalDesktop = null }; // Act @@ -191,5 +195,28 @@ await StreamToStringHelper.StreamToStringAsync( Assert.AreEqual(testFolderPath, fileResult2.App.StorageFolder); Assert.IsTrue(fileResult2.App.Verbose); } + + [TestMethod] + public async Task UpdateAppSettingsAsync_ValidInput_Success1() + { + var storage = new FakeIStorage(); + var selectorStorage = new FakeSelectorStorage(storage); + var updateAppSettingsByPath = + new UpdateAppSettingsByPath(new AppSettings(), selectorStorage); + var appSettingTransferObject = new AppSettingsTransferObject + { + DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Raw, + Verbose = true + }; + + // Act + var result = + await updateAppSettingsByPath.UpdateAppSettingsAsync(appSettingTransferObject); + + + // Assert + Assert.AreEqual(200, result.StatusCode); + Assert.AreEqual("Updated", result.Message); + } } } From 53a0e52911910faefcf106dd9722a26ad3871b22 Mon Sep 17 00:00:00 2001 From: Dion Date: Sat, 24 Feb 2024 19:44:53 +0100 Subject: [PATCH 106/125] add code --- .../preference-app-settings-desktop.tsx | 54 ++++++++++++------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx index c08358091a..2b1b4f435a 100644 --- a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { ChangeEvent, useState } from "react"; import useFetch from "../../../hooks/use-fetch"; import useGlobalSettings from "../../../hooks/use-global-settings"; import { IAppSettings } from "../../../interfaces/IAppSettings"; @@ -10,6 +10,38 @@ import { UrlQuery } from "../../../shared/url-query"; import FormControl from "../../atoms/form-control/form-control"; import SwitchButton from "../../atoms/switch-button/switch-button"; +async function updateDefaultEditorPhotos( + event: ChangeEvent, + setIsMessage: React.Dispatch> +) { + const bodyParams = new URLSearchParams(); + bodyParams.set("desktopC1111ollectionsOpen", event.target.innerText); + + const result = await FetchPost(new UrlQuery().UrlApiAppSettings(), bodyParams.toString()); + if (result.statusCode != 200) { + setIsMessage("FAIL"); + return; + } + setIsMessage("OK"); +} + +async function toggleCollections( + value: boolean, + setIsMessage: React.Dispatch> +) { + const desktopCollectionsOpen = value ? RawJpegMode.Raw : RawJpegMode.Jpeg; + + const bodyParams = new URLSearchParams(); + bodyParams.set("desktopCollectionsOpen", desktopCollectionsOpen.toString()); + + const result = await FetchPost(new UrlQuery().UrlApiAppSettings(), bodyParams.toString()); + if (result.statusCode != 200) { + setIsMessage("FAIL"); + return; + } + setIsMessage("OK"); +} + const PreferencesAppSettingsDesktop: React.FunctionComponent = () => { const appSettings = useFetch(new UrlQuery().UrlApiAppSettings(), "get") ?.data as IAppSettings | null; @@ -28,20 +60,6 @@ const PreferencesAppSettingsDesktop: React.FunctionComponent = () => { // List DefaultDesktopEditor // CollectionsOpenType.RawJpegMode DesktopCollectionsOpen - async function toggleCollections(value: boolean) { - const desktopCollectionsOpen = value ? RawJpegMode.Raw : RawJpegMode.Jpeg; - - const bodyParams = new URLSearchParams(); - bodyParams.set("desktopCollectionsOpen", desktopCollectionsOpen.toString()); - - const result = await FetchPost(new UrlQuery().UrlApiAppSettings(), bodyParams.toString()); - if (result.statusCode != 200) { - setIsMessage("FAIL"); - return; - } - setIsMessage("OK"); - } - // for showing a notification const [isMessage, setIsMessage] = useState(""); @@ -57,13 +75,13 @@ const PreferencesAppSettingsDesktop: React.FunctionComponent = () => { data-test="desktop-collections-open-toggle" isEnabled={true} leftLabel={MessageSwitchButtonDesktopCollectionsJpegDefaultOff} - onToggle={toggleCollections} + onToggle={(value) => toggleCollections(value, setIsMessage)} rightLabel={MessageSwitchButtonDesktopCollectionsRawOn} /> -

Tags:

+

Default application to edit photos:

updateDefaultEditorPhotos(value, setIsMessage)} name="tags" contentEditable={true} > From 3f49be52dbbc7bc0d9cc3b0e86b483515fc2e772 Mon Sep 17 00:00:00 2001 From: Dion Date: Sun, 25 Feb 2024 12:35:33 +0100 Subject: [PATCH 107/125] fix tests --- .../Services/UpdateAppSettingsByPathTest.cs | 18 ++++----- .../Models/AppSettingsTransferObjectTest.cs | 37 +++++++++++++++++++ 2 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 starsky/starskytest/starsky.foundation.platform/Models/AppSettingsTransferObjectTest.cs diff --git a/starsky/starskytest/starsky.feature.settings/Services/UpdateAppSettingsByPathTest.cs b/starsky/starskytest/starsky.feature.settings/Services/UpdateAppSettingsByPathTest.cs index 41f0ccecbf..e5cc6f148a 100644 --- a/starsky/starskytest/starsky.feature.settings/Services/UpdateAppSettingsByPathTest.cs +++ b/starsky/starskytest/starsky.feature.settings/Services/UpdateAppSettingsByPathTest.cs @@ -32,8 +32,7 @@ public async Task UpdateAppSettingsAsync_ValidInput_Success() new UpdateAppSettingsByPath(new AppSettings(), selectorStorage); var appSettingTransferObject = new AppSettingsTransferObject { - StorageFolder = testFolderPath, - Verbose = true + StorageFolder = testFolderPath, Verbose = true }; // Act @@ -55,8 +54,8 @@ public async Task UpdateAppSettingsAsync_ValidInput_Success_CompareJson() var before = Environment.GetEnvironmentVariable("app__storageFolder"); Environment.SetEnvironmentVariable("app__storageFolder", string.Empty); - var testFolderPath = Path.DirectorySeparatorChar.ToString() + "test" + - Path.DirectorySeparatorChar.ToString(); + var testFolderPath = Path.DirectorySeparatorChar + "test" + + Path.DirectorySeparatorChar; var storage = new FakeIStorage(new List { "/", testFolderPath }); var selectorStorage = new FakeSelectorStorage(storage); @@ -66,7 +65,6 @@ public async Task UpdateAppSettingsAsync_ValidInput_Success_CompareJson() { StorageFolder = testFolderPath, Verbose = true, - UseLocalDesktop = null }; // Act @@ -86,9 +84,10 @@ public async Task UpdateAppSettingsAsync_ValidInput_Success_CompareJson() // Assert var expectedResult = "{\n \"app\": {\n \"Verbose\": \"true\",\n \"StorageFolder\": " + // rm quotes - storageFolderJson + ",\n \"UseLocalDesktop\": \"false\"\n }\n}"; + storageFolderJson + ",\n"; - Assert.AreEqual(expectedResult, result); + + Assert.AreEqual(true, result.Contains(expectedResult)); } [TestMethod] @@ -195,7 +194,7 @@ await StreamToStringHelper.StreamToStringAsync( Assert.AreEqual(testFolderPath, fileResult2.App.StorageFolder); Assert.IsTrue(fileResult2.App.Verbose); } - + [TestMethod] public async Task UpdateAppSettingsAsync_ValidInput_Success1() { @@ -205,8 +204,7 @@ public async Task UpdateAppSettingsAsync_ValidInput_Success1() new UpdateAppSettingsByPath(new AppSettings(), selectorStorage); var appSettingTransferObject = new AppSettingsTransferObject { - DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Raw, - Verbose = true + DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Raw, Verbose = true }; // Act diff --git a/starsky/starskytest/starsky.foundation.platform/Models/AppSettingsTransferObjectTest.cs b/starsky/starskytest/starsky.foundation.platform/Models/AppSettingsTransferObjectTest.cs new file mode 100644 index 0000000000..222f66acda --- /dev/null +++ b/starsky/starskytest/starsky.foundation.platform/Models/AppSettingsTransferObjectTest.cs @@ -0,0 +1,37 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using starsky.foundation.platform.Helpers; +using starsky.foundation.platform.Models; + +namespace starskytest.starsky.foundation.platform.Models; + +[TestClass] +public class AppSettingsTransferObjectTest +{ + [TestMethod] + public void AppSettingsTransferObject_Verbose() + { + var appSettingsTransferObject = new AppSettingsTransferObject + { + Verbose = true, + StorageFolder = "test", + UseSystemTrash = true, + UseLocalDesktop = true, + DefaultDesktopEditor = + [ + new AppSettingsDefaultEditorApplication + { + ApplicationPath = "app", + ImageFormats = [ExtensionRolesHelper.ImageFormat.bmp] + } + ] + }; + + Assert.AreEqual(true, appSettingsTransferObject.Verbose); + Assert.AreEqual("test", appSettingsTransferObject.StorageFolder); + Assert.AreEqual(true, appSettingsTransferObject.UseSystemTrash); + Assert.AreEqual(true, appSettingsTransferObject.UseLocalDesktop); + Assert.AreEqual("app", appSettingsTransferObject.DefaultDesktopEditor[0].ApplicationPath); + Assert.AreEqual(ExtensionRolesHelper.ImageFormat.bmp, + appSettingsTransferObject.DefaultDesktopEditor[0].ImageFormats[0]); + } +} From 48b8e180baa943119642b9658ac493d6167bd7c3 Mon Sep 17 00:00:00 2001 From: Dion Date: Sun, 25 Feb 2024 14:31:57 +0100 Subject: [PATCH 108/125] updates --- .../preference-app-settings-desktop.tsx | 103 +++++++++++++----- .../src/localization/localization.json | 17 ++- .../clientapp/src/style/css/20-content.css | 5 + 3 files changed, 98 insertions(+), 27 deletions(-) diff --git a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx index 2b1b4f435a..a4801839bc 100644 --- a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx @@ -2,7 +2,9 @@ import React, { ChangeEvent, useState } from "react"; import useFetch from "../../../hooks/use-fetch"; import useGlobalSettings from "../../../hooks/use-global-settings"; import { IAppSettings } from "../../../interfaces/IAppSettings"; +import { IAppSettingsDefaultEditorApplication } from "../../../interfaces/IAppSettingsDefaultEditorApplication"; import { RawJpegMode } from "../../../interfaces/ICollectionsOpenType"; +import { ImageFormat } from "../../../interfaces/IFileIndexItem"; import localization from "../../../localization/localization.json"; import FetchPost from "../../../shared/fetch/fetch-post"; import { Language } from "../../../shared/language"; @@ -10,12 +12,40 @@ import { UrlQuery } from "../../../shared/url-query"; import FormControl from "../../atoms/form-control/form-control"; import SwitchButton from "../../atoms/switch-button/switch-button"; +const defaultEditorApplication = { + imageFormats: [ImageFormat.jpg, ImageFormat.png, ImageFormat.bmp, ImageFormat.tiff] +} as IAppSettingsDefaultEditorApplication; + async function updateDefaultEditorPhotos( event: ChangeEvent, - setIsMessage: React.Dispatch> + setIsMessage: React.Dispatch>, + defaultDesktopEditor?: IAppSettingsDefaultEditorApplication[] ) { + if (!defaultDesktopEditor) { + setIsMessage("FAIL"); + return; + } const bodyParams = new URLSearchParams(); - bodyParams.set("desktopC1111ollectionsOpen", event.target.innerText); + + defaultEditorApplication.applicationPath = event.target.innerText; + const index = defaultDesktopEditor.findIndex( + (p) => p.imageFormats.includes(ImageFormat.jpg) || p.imageFormats.includes(ImageFormat.tiff) + ); + if (index === -1) { + defaultDesktopEditor.push(defaultEditorApplication); + } else { + defaultDesktopEditor[index] = defaultEditorApplication; + } + + defaultDesktopEditor.forEach((editorApp, index) => { + defaultEditorApplication.imageFormats.forEach((imageFormat, idx) => { + bodyParams.append( + `DefaultDesktopEditor[${index}].ImageFormats[${idx}]`, + imageFormat.toString() + ); + }); + bodyParams.append(`DefaultDesktopEditor[${index}].ApplicationPath`, editorApp.applicationPath); + }); const result = await FetchPost(new UrlQuery().UrlApiAppSettings(), bodyParams.toString()); if (result.statusCode != 200) { @@ -48,6 +78,10 @@ const PreferencesAppSettingsDesktop: React.FunctionComponent = () => { const settings = useGlobalSettings(); + const imageDefaultEditor = appSettings?.defaultDesktopEditor.find( + (p) => p.imageFormats.includes(ImageFormat.jpg) || p.imageFormats.includes(ImageFormat.tiff) + ); + const language = new Language(settings.language); const MessageSwitchButtonDesktopCollectionsRawOn = language.key( localization.MessageSwitchButtonDesktopCollectionsRawOn @@ -55,36 +89,53 @@ const PreferencesAppSettingsDesktop: React.FunctionComponent = () => { const MessageSwitchButtonDesktopCollectionsJpegDefaultOff = language.key( localization.MessageSwitchButtonDesktopCollectionsJpegDefaultOff ); - - // appSettings?.desktopCollectionsOpen - // List DefaultDesktopEditor - // CollectionsOpenType.RawJpegMode DesktopCollectionsOpen + const MessageSwitchButtonDesktopApplication = language.key( + localization.MessageSwitchButtonDesktopApplication + ); + const MessageSwitchButtonDesktopApplicationDescription = language.key( + localization.MessageSwitchButtonDesktopApplicationDescription + ); + const MessageAppSettingDefaultEditorPhotos = language.key( + localization.MessageAppSettingDefaultEditorPhotos + ); + const MessageAppSettingDefaultEditorPhotosDescription = language.key( + localization.MessageAppSettingDefaultEditorPhotosDescription + ); // for showing a notification const [isMessage, setIsMessage] = useState(""); return ( <> -

Desktop

- Only used when using Starsky as desktop - {isMessage !== "" ? ( -
{isMessage}
- ) : null} - toggleCollections(value, setIsMessage)} - rightLabel={MessageSwitchButtonDesktopCollectionsRawOn} - /> -

Default application to edit photos:

- updateDefaultEditorPhotos(value, setIsMessage)} - name="tags" - contentEditable={true} - > +
{MessageSwitchButtonDesktopApplication}
+
+

{MessageSwitchButtonDesktopApplicationDescription}

+ + {isMessage !== "" ? ( +
{isMessage}
+ ) : null} + toggleCollections(value, setIsMessage)} + rightLabel={MessageSwitchButtonDesktopCollectionsRawOn} + /> + +

{MessageAppSettingDefaultEditorPhotos}

+

{MessageAppSettingDefaultEditorPhotosDescription}

+ + updateDefaultEditorPhotos(value, setIsMessage, appSettings?.defaultDesktopEditor) + } + name="tags" + contentEditable={true} + > + {imageDefaultEditor?.applicationPath} + +
); }; diff --git a/starsky/starsky/clientapp/src/localization/localization.json b/starsky/starsky/clientapp/src/localization/localization.json index d7a2728441..2a0ed1016e 100644 --- a/starsky/starsky/clientapp/src/localization/localization.json +++ b/starsky/starsky/clientapp/src/localization/localization.json @@ -291,7 +291,22 @@ "en": "Jpeg first", "nl": "Jpeg eerst" }, - + "MessageSwitchButtonDesktopApplication": { + "en": "AppSettings: Desktop application", + "nl": "AppSettings: Desktop applicatie" + }, + "MessageSwitchButtonDesktopApplicationDescription": { + "en": "These settings are only available when using as Desktop application", + "nl": "Deze instellingen zijn alleen beschikbaar als desktop applicatie" + }, + "MessageAppSettingDefaultEditorPhotos": { + "en": "Default application to edit photos:", + "nl": "Standaardtoepassing om foto's te bewerken:" + }, + "MessageAppSettingDefaultEditorPhotosDescription": { + "en": "Keep emthy to use the default system application ", + "nl": "Hou leeg om de standaardtoepassing van het systeem te gebruiken" + }, "temp1": { "en": "", "nl": "" diff --git a/starsky/starsky/clientapp/src/style/css/20-content.css b/starsky/starsky/clientapp/src/style/css/20-content.css index 06bf4910c1..16bb2ba57e 100644 --- a/starsky/starsky/clientapp/src/style/css/20-content.css +++ b/starsky/starsky/clientapp/src/style/css/20-content.css @@ -100,6 +100,11 @@ .content--text { padding: 10px; } + +.content--text.no-left-padding { + padding-left: 0px; +} + .content--text .date { display: inline-block; margin-right: 10px; From 12a88669f0988d6490f2d0dd01a70d0de4927b1c Mon Sep 17 00:00:00 2001 From: Dion Date: Sun, 25 Feb 2024 21:12:11 +0100 Subject: [PATCH 109/125] add some texts --- .../preference-app-settings-desktop.tsx | 22 ++++++++++++++++--- .../src/localization/localization.json | 8 +++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx index a4801839bc..d64f9fdbf1 100644 --- a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx @@ -19,6 +19,8 @@ const defaultEditorApplication = { async function updateDefaultEditorPhotos( event: ChangeEvent, setIsMessage: React.Dispatch>, + MessageSwitchButtonDesktopCollectionsUpdateError: string, + MessageSwitchButtonDesktopCollectionsUpdateSuccess: string, defaultDesktopEditor?: IAppSettingsDefaultEditorApplication[] ) { if (!defaultDesktopEditor) { @@ -49,10 +51,10 @@ async function updateDefaultEditorPhotos( const result = await FetchPost(new UrlQuery().UrlApiAppSettings(), bodyParams.toString()); if (result.statusCode != 200) { - setIsMessage("FAIL"); + setIsMessage(MessageSwitchButtonDesktopCollectionsUpdateError); return; } - setIsMessage("OK"); + setIsMessage(MessageSwitchButtonDesktopCollectionsUpdateSuccess); } async function toggleCollections( @@ -102,6 +104,14 @@ const PreferencesAppSettingsDesktop: React.FunctionComponent = () => { localization.MessageAppSettingDefaultEditorPhotosDescription ); + const MessageSwitchButtonDesktopCollectionsUpdateError = language.key( + localization.MessageSwitchButtonDesktopCollectionsUpdateError + ); + + const MessageSwitchButtonDesktopCollectionsUpdateSuccess = language.key( + localization.MessageSwitchButtonDesktopCollectionsUpdateSuccess + ); + // for showing a notification const [isMessage, setIsMessage] = useState(""); @@ -128,7 +138,13 @@ const PreferencesAppSettingsDesktop: React.FunctionComponent = () => { - updateDefaultEditorPhotos(value, setIsMessage, appSettings?.defaultDesktopEditor) + updateDefaultEditorPhotos( + value, + setIsMessage, + MessageSwitchButtonDesktopCollectionsUpdateError, + MessageSwitchButtonDesktopCollectionsUpdateSuccess, + appSettings?.defaultDesktopEditor + ) } name="tags" contentEditable={true} diff --git a/starsky/starsky/clientapp/src/localization/localization.json b/starsky/starsky/clientapp/src/localization/localization.json index 2a0ed1016e..c1600bb9c9 100644 --- a/starsky/starsky/clientapp/src/localization/localization.json +++ b/starsky/starsky/clientapp/src/localization/localization.json @@ -307,6 +307,14 @@ "en": "Keep emthy to use the default system application ", "nl": "Hou leeg om de standaardtoepassing van het systeem te gebruiken" }, + "MessageSwitchButtonDesktopCollectionsUpdateError": { + "en": "Something went wrong updating the collection preferences", + "nl": "Er ging iets met het updaten van de collection voorkeur " + }, + "MessageSwitchButtonDesktopCollectionsUpdateSuccess": { + "en": "The collection preference is updated", + "nl": "De collection voorkeur is bijgewerkt" + }, "temp1": { "en": "", "nl": "" From 483036f498853cc92e990d694ad91474f0ac0ac2 Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 26 Feb 2024 19:50:15 +0100 Subject: [PATCH 110/125] add test / move translations --- .../Interfaces/IUserManager.cs | 2 + .../Models/Account/UserStatusModel.cs | 1 + .../Services/UserManager.cs | 8 +++ .../starsky/Controllers/AccountController.cs | 6 +- .../preference-app-settings-desktop.tsx | 49 ++++++------- ...references-app-settings-storage-folder.tsx | 7 +- .../preferences-password.tsx | 43 +++--------- .../preferences-username.tsx | 10 ++- .../src/containers/account-register.tsx | 26 +++---- .../clientapp/src/interfaces/IAppSettings.ts | 1 + .../src/localization/localization.json | 68 +++++++++++++++++++ .../starsky/clientapp/src/shared/url-query.ts | 4 ++ .../Services/UpdateAppSettingsByPathTest.cs | 30 ++++++-- 13 files changed, 167 insertions(+), 88 deletions(-) diff --git a/starsky/starsky.foundation.accountmanagement/Interfaces/IUserManager.cs b/starsky/starsky.foundation.accountmanagement/Interfaces/IUserManager.cs index 05c63af249..17496fa95d 100644 --- a/starsky/starsky.foundation.accountmanagement/Interfaces/IUserManager.cs +++ b/starsky/starsky.foundation.accountmanagement/Interfaces/IUserManager.cs @@ -96,6 +96,8 @@ Task RemoveUser(string credentialTypeCode, Task ExistAsync(int userTableId); Role? GetRole(string credentialTypeCode, string identifier); + + Task GetRoleAsync(int userId); bool PreflightValidate(string userName, string password, string confirmPassword); } } diff --git a/starsky/starsky.foundation.accountmanagement/Models/Account/UserStatusModel.cs b/starsky/starsky.foundation.accountmanagement/Models/Account/UserStatusModel.cs index 73268b6b78..0e16160598 100644 --- a/starsky/starsky.foundation.accountmanagement/Models/Account/UserStatusModel.cs +++ b/starsky/starsky.foundation.accountmanagement/Models/Account/UserStatusModel.cs @@ -10,5 +10,6 @@ public sealed class UserIdentifierStatusModel public DateTime Created { get; set; } public List? CredentialsIdentifiers { get; set; } = new List(); public List? CredentialTypeIds { get; set; } = new List(); + public string? RoleCode { get; set; } } } diff --git a/starsky/starsky.foundation.accountmanagement/Services/UserManager.cs b/starsky/starsky.foundation.accountmanagement/Services/UserManager.cs index fac0da02e7..2552255262 100644 --- a/starsky/starsky.foundation.accountmanagement/Services/UserManager.cs +++ b/starsky/starsky.foundation.accountmanagement/Services/UserManager.cs @@ -763,6 +763,14 @@ public int GetCurrentUserId(HttpContext httpContext) return _dbContext.Roles.TagWith("GetRole").FirstOrDefault(p => p.Id == roleId); } + public async Task GetRoleAsync(int userId) + { + var role = await _dbContext.UserRoles.FirstOrDefaultAsync(p => p.User != null && p.User.Id == userId); + if ( role == null ) return null; + var roleId = role.RoleId; + return _dbContext.Roles.TagWith("GetRole").FirstOrDefault(p => p.Id == roleId); + } + public Credential? GetCredentialsByUserId(int userId) { return _dbContext.Credentials diff --git a/starsky/starsky/Controllers/AccountController.cs b/starsky/starsky/Controllers/AccountController.cs index fcf8c8e262..b723387ccb 100644 --- a/starsky/starsky/Controllers/AccountController.cs +++ b/starsky/starsky/Controllers/AccountController.cs @@ -88,9 +88,13 @@ public async Task Status() model.CredentialTypeIds = null; return Json(model); } - + model.CredentialsIdentifiers?.Add(credentials.Identifier!); model.CredentialTypeIds?.Add(credentials.CredentialTypeId); + + var role = await _userManager.GetRoleAsync(currentUser.Id); + model.RoleCode = role?.Code; + return Json(model); } diff --git a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx index d64f9fdbf1..c6c74d3d19 100644 --- a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx @@ -75,8 +75,15 @@ async function toggleCollections( } const PreferencesAppSettingsDesktop: React.FunctionComponent = () => { + // Get AppSettings from backend const appSettings = useFetch(new UrlQuery().UrlApiAppSettings(), "get") ?.data as IAppSettings | null; + // roles + const permissionsData = useFetch(new UrlQuery().UrlAccountPermissions(), "get"); + + const isAppSettingsWrite = permissionsData?.data?.includes( + new UrlQuery().KeyAccountPermissionAppSettingsWrite() + ); const settings = useGlobalSettings(); @@ -85,32 +92,12 @@ const PreferencesAppSettingsDesktop: React.FunctionComponent = () => { ); const language = new Language(settings.language); - const MessageSwitchButtonDesktopCollectionsRawOn = language.key( - localization.MessageSwitchButtonDesktopCollectionsRawOn - ); - const MessageSwitchButtonDesktopCollectionsJpegDefaultOff = language.key( - localization.MessageSwitchButtonDesktopCollectionsJpegDefaultOff - ); const MessageSwitchButtonDesktopApplication = language.key( localization.MessageSwitchButtonDesktopApplication ); const MessageSwitchButtonDesktopApplicationDescription = language.key( localization.MessageSwitchButtonDesktopApplicationDescription ); - const MessageAppSettingDefaultEditorPhotos = language.key( - localization.MessageAppSettingDefaultEditorPhotos - ); - const MessageAppSettingDefaultEditorPhotosDescription = language.key( - localization.MessageAppSettingDefaultEditorPhotosDescription - ); - - const MessageSwitchButtonDesktopCollectionsUpdateError = language.key( - localization.MessageSwitchButtonDesktopCollectionsUpdateError - ); - - const MessageSwitchButtonDesktopCollectionsUpdateSuccess = language.key( - localization.MessageSwitchButtonDesktopCollectionsUpdateSuccess - ); // for showing a notification const [isMessage, setIsMessage] = useState(""); @@ -119,7 +106,11 @@ const PreferencesAppSettingsDesktop: React.FunctionComponent = () => { <>
{MessageSwitchButtonDesktopApplication}
-

{MessageSwitchButtonDesktopApplicationDescription}

+ {appSettings?.useLocalDesktop ? ( +

{MessageSwitchButtonDesktopApplicationDescription}

+ ) : ( +
{MessageSwitchButtonDesktopApplicationDescription}
+ )} {isMessage !== "" ? (
{isMessage}
@@ -127,27 +118,27 @@ const PreferencesAppSettingsDesktop: React.FunctionComponent = () => { toggleCollections(value, setIsMessage)} - rightLabel={MessageSwitchButtonDesktopCollectionsRawOn} + rightLabel={language.key(localization.MessageSwitchButtonDesktopCollectionsRawOn)} /> -

{MessageAppSettingDefaultEditorPhotos}

-

{MessageAppSettingDefaultEditorPhotosDescription}

+

{language.key(localization.MessageAppSettingDefaultEditorPhotos)}

+

{language.key(localization.MessageAppSettingDefaultEditorPhotosDescription)}

updateDefaultEditorPhotos( value, setIsMessage, - MessageSwitchButtonDesktopCollectionsUpdateError, - MessageSwitchButtonDesktopCollectionsUpdateSuccess, + language.key(localization.MessageSwitchButtonDesktopCollectionsUpdateError), + language.key(localization.MessageSwitchButtonDesktopCollectionsUpdateSuccess), appSettings?.defaultDesktopEditor ) } name="tags" - contentEditable={true} + contentEditable={appSettings?.useLocalDesktop === true && isAppSettingsWrite} > {imageDefaultEditor?.applicationPath} diff --git a/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.tsx b/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.tsx index 17d8702a5f..6ea84e279e 100644 --- a/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/preferences-app-settings-storage-folder/preferences-app-settings-storage-folder.tsx @@ -47,7 +47,8 @@ const PreferencesAppSettingsStorageFolder: React.FunctionComponent = () => { if (!permissionsData?.data?.includes || permissionsData?.statusCode !== 200) { return false; } - return permissionsData.data.includes("AppSettingsWrite"); + // AppSettingsWrite + return permissionsData.data.includes(new UrlQuery().KeyAccountPermissionAppSettingsWrite()); } setIsEnabled(permissions()); @@ -66,7 +67,9 @@ const PreferencesAppSettingsStorageFolder: React.FunctionComponent = () => { return ( <> -
{MessageAppSettingsEntireAppScope}
+
+ {MessageAppSettingsEntireAppScope} +

{MessageAppSettingsStorageFolder}

= () => { +const PreferencesPassword: React.FunctionComponent = () => { const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageChangePassword = language.text("Verander je wachtwoord", "Change your password"); - - const MessageExamplePassword = language.text("superveilig", "supersafe"); - const MessageCurrentPassword = language.text( - "Geef je huidige wachtwoord op", - "Enter your current password" - ); - const MessageChangedPassword = language.text( - "Geef je nieuwe wachtwoord op", - "Enter your new password" - ); - const MessageChangedConfirmPassword = language.text( - "Herhaal je nieuwe wachtwoord", - "And your new password again" - ); - const MessageNoPassword = language.text( - "Voer het huidige en nieuwe wachtwoord in", - "Enter the current and new password" - ); - const MessagePasswordChanged = language.text( - "Je wachtwoord is succesvol veranderd", - "Your password has been successfully changed" - ); - const MessagePasswordNoMatch = language.text( - "De wachtwoorden komen niet overeen", - "The passwords do not match" - ); - const MessagePasswordModalError = language.text( - "Het nieuwe wachtwoord voldoet niet aan de criteria", - "The new password does not meet the criteria" - ); + const MessageChangePassword = language.key(localization.MessageChangedPassword); + const MessageExamplePassword = language.key(localization.MessageExamplePassword); + const MessageCurrentPassword = language.key(localization.MessageCurrentPassword); + const MessageChangedPassword = language.key(localization.MessageChangedPassword); + const MessageChangedConfirmPassword = language.key(localization.MessageChangedConfirmPassword); + const MessageNoPassword = language.key(localization.MessageNoPassword); + const MessagePasswordChanged = language.key(localization.MessagePasswordChanged); + const MessagePasswordNoMatch = language.key(localization.MessagePasswordNoMatch); + const MessagePasswordModalError = language.key(localization.MessagePasswordModalError); const [loading, setLoading] = useState(false); diff --git a/starsky/starsky/clientapp/src/components/organisms/preferences-username/preferences-username.tsx b/starsky/starsky/clientapp/src/components/organisms/preferences-username/preferences-username.tsx index f9ce1ad529..e789e3d445 100644 --- a/starsky/starsky/clientapp/src/components/organisms/preferences-username/preferences-username.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/preferences-username/preferences-username.tsx @@ -1,14 +1,16 @@ import React, { useEffect } from "react"; import useFetch from "../../../hooks/use-fetch"; import useGlobalSettings from "../../../hooks/use-global-settings"; +import localization from "../../../localization/localization.json"; import { Language } from "../../../shared/language"; import { UrlQuery } from "../../../shared/url-query"; -const PreferencesUsername: React.FunctionComponent = () => { +const PreferencesUsername: React.FunctionComponent = () => { const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageUnknownUsername = language.text("Onbekende gebruikersnaam", "Unknown username"); - const MessageUsername = language.text("Gebruikersnaam", "Username"); + const MessageUnknownUsername = language.key(localization.MessageUnknownUsername); + const MessageUsername = language.key(localization.MessageUsername); + const MessageRole = language.key(localization.MessageRole); const accountStatus = useFetch(new UrlQuery().UrlAccountStatus(), "get"); const [userName, setUserName] = React.useState(MessageUnknownUsername); @@ -32,6 +34,8 @@ const PreferencesUsername: React.FunctionComponent = () => { > {userName}
+
{MessageRole}
+
{accountStatus?.data?.roleCode}
); }; diff --git a/starsky/starsky/clientapp/src/containers/account-register.tsx b/starsky/starsky/clientapp/src/containers/account-register.tsx index 9a6062145a..453ec9fafd 100644 --- a/starsky/starsky/clientapp/src/containers/account-register.tsx +++ b/starsky/starsky/clientapp/src/containers/account-register.tsx @@ -2,6 +2,7 @@ import React, { FunctionComponent, useEffect } from "react"; import ButtonStyled from "../components/atoms/button-styled/button-styled"; import useGlobalSettings from "../hooks/use-global-settings"; import useLocation from "../hooks/use-location/use-location"; +import localization from "../localization/localization.json"; import { DocumentTitle } from "../shared/document-title"; import FetchGet from "../shared/fetch/fetch-get"; import FetchPost from "../shared/fetch/fetch-post"; @@ -14,23 +15,14 @@ const AccountRegister: FunctionComponent = () => { const language = new Language(settings.language); const MessageApplicationName = "Starsky"; - const MessageCreateNewAccount = language.text("Maak nieuw account", "Create new account"); - const MessageUsername = language.text("E-mailadres", "E-mail address"); - const MessageExamplePassword = language.text("superveilig", "supersafe"); - const MessageExampleUsername = "dont@mail.me"; - const MessagePassword = language.text("Geef je wachtwoord op", "Enter your password"); - const MessageConfirmPassword = language.text( - "Vul je wachtwoord nog een keer in", - "Enter your password again" - ); - const MessageNoUsernamePassword = language.text( - "Voer een emailadres en een wachtwoord in", - "Enter an email address and password" - ); - const MessageWrongFormatEmailAddress = language.text( - "Controleer je email adres", - "Check your email address" - ); + const MessageCreateNewAccount = language.key(localization.MessageCreateNewAccount); + const MessageUsername = language.key(localization.MessageUsername); + const MessageExamplePassword = language.key(localization.MessageExamplePassword); + const MessageExampleUsername = language.key(localization.MessageExampleUsername); + const MessagePassword = language.key(localization.MessagePassword); + const MessageConfirmPassword = language.key(localization.MessageConfirmPassword); + const MessageNoUsernamePassword = language.key(localization.MessageNoUsernamePassword); + const MessageWrongFormatEmailAddress = language.key(localization.MessageWrongFormatEmailAddress); const MessagePasswordToShort = language.text( "Gebruik minimaal 8 tekens voor je wachtwoord", "Use at least 8 characters for your password" diff --git a/starsky/starsky/clientapp/src/interfaces/IAppSettings.ts b/starsky/starsky/clientapp/src/interfaces/IAppSettings.ts index cda5da48f5..1e87e498f6 100644 --- a/starsky/starsky/clientapp/src/interfaces/IAppSettings.ts +++ b/starsky/starsky/clientapp/src/interfaces/IAppSettings.ts @@ -5,6 +5,7 @@ export interface IAppSettings { verbose: boolean; storageFolder: string; storageFolderAllowEdit: boolean; + useLocalDesktop: boolean; defaultDesktopEditor: IAppSettingsDefaultEditorApplication[]; desktopCollectionsOpen: RawJpegMode; } diff --git a/starsky/starsky/clientapp/src/localization/localization.json b/starsky/starsky/clientapp/src/localization/localization.json index c1600bb9c9..421f490574 100644 --- a/starsky/starsky/clientapp/src/localization/localization.json +++ b/starsky/starsky/clientapp/src/localization/localization.json @@ -315,6 +315,74 @@ "en": "The collection preference is updated", "nl": "De collection voorkeur is bijgewerkt" }, + "MessageUnknownUsername": { + "en": "Unknown username", + "nl": "Onbekende gebruikersnaam" + }, + "MessageUsername": { + "en": "Username", + "nl": "Gebruikersnaam" + }, + "MessageRole": { + "en": "Role", + "nl": "Rol" + }, + "MessageExamplePassword": { + "en": "supersafe", + "nl": "superveilig" + }, + "MessageCurrentPassword": { + "en": "Enter your current password", + "nl": "Geef je huidige wachtwoord op" + }, + "MessageChangedPassword": { + "en": "Enter your new password", + "nl": "Geef je nieuwe wachtwoord op" + }, + "MessageChangedConfirmPassword": { + "en": "And your new password again", + "nl": "Herhaal je nieuwe wachtwoord" + }, + "MessageNoPassword": { + "en": "Enter the current and new password", + "nl": "Voer het huidige en nieuwe wachtwoord in" + }, + "MessagePassword": { + "en": "Enter your password", + "nl": "Geef je wachtwoord op" + }, + "MessagePasswordChanged": { + "en": "Your password has been successfully changed", + "nl": "Je wachtwoord is succesvol veranderd" + }, + "MessagePasswordNoMatch": { + "en": "The passwords do not match", + "nl": "De wachtwoorden komen niet overeen" + }, + "MessagePasswordModalError": { + "en": "The new password does not meet the criteria", + "nl": "Het nieuwe wachtwoord voldoet niet aan de criteria" + }, + "MessageCreateNewAccount": { + "nl": "Maak nieuw account", + "en": "Create new account" + }, + "MessageExampleUsername": { + "en": "dont@mail.me", + "nl": "dont@mail.me" + }, + "MessageConfirmPassword": { + "en": "Enter your password again", + "nl": "Vul je wachtwoord nog een keer in" + }, + "MessageNoUsernamePassword": { + "en": "Enter an email address and password", + "nl": "Voer een emailadres en een wachtwoord in" + }, + "MessageWrongFormatEmailAddress": { + "en": "Check your email address", + "nl": "Controleer je email adres" + }, "temp1": { "en": "", "nl": "" diff --git a/starsky/starsky/clientapp/src/shared/url-query.ts b/starsky/starsky/clientapp/src/shared/url-query.ts index 81015ab7cf..8a8bf79e00 100644 --- a/starsky/starsky/clientapp/src/shared/url-query.ts +++ b/starsky/starsky/clientapp/src/shared/url-query.ts @@ -144,6 +144,10 @@ export class UrlQuery { return `${this.prefix}/api/account/permissions`; }; + public KeyAccountPermissionAppSettingsWrite = (): string => { + return "AppSettingsWrite"; + }; + /** * Keep colorClass in URL */ diff --git a/starsky/starskytest/starsky.feature.settings/Services/UpdateAppSettingsByPathTest.cs b/starsky/starskytest/starsky.feature.settings/Services/UpdateAppSettingsByPathTest.cs index e5cc6f148a..395ac98b70 100644 --- a/starsky/starskytest/starsky.feature.settings/Services/UpdateAppSettingsByPathTest.cs +++ b/starsky/starskytest/starsky.feature.settings/Services/UpdateAppSettingsByPathTest.cs @@ -6,6 +6,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using starsky.feature.settings.Services; using starsky.foundation.platform.Enums; +using starsky.foundation.platform.Helpers; using starsky.foundation.platform.JsonConverter; using starsky.foundation.platform.Models; using starsky.foundation.storage.Helpers; @@ -63,8 +64,7 @@ public async Task UpdateAppSettingsAsync_ValidInput_Success_CompareJson() var updateAppSettingsByPath = new UpdateAppSettingsByPath(appSettings, selectorStorage); var appSettingTransferObject = new AppSettingsTransferObject { - StorageFolder = testFolderPath, - Verbose = true, + StorageFolder = testFolderPath, Verbose = true, }; // Act @@ -196,7 +196,7 @@ await StreamToStringHelper.StreamToStringAsync( } [TestMethod] - public async Task UpdateAppSettingsAsync_ValidInput_Success1() + public async Task UpdateAppSettingsAsync_ValidInput_Success_Desktop() { var storage = new FakeIStorage(); var selectorStorage = new FakeSelectorStorage(storage); @@ -204,7 +204,16 @@ public async Task UpdateAppSettingsAsync_ValidInput_Success1() new UpdateAppSettingsByPath(new AppSettings(), selectorStorage); var appSettingTransferObject = new AppSettingsTransferObject { - DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Raw, Verbose = true + DesktopCollectionsOpen = CollectionsOpenType.RawJpegMode.Raw, + DefaultDesktopEditor = + [ + new AppSettingsDefaultEditorApplication + { + ApplicationPath = "/test", + ImageFormats = + [ExtensionRolesHelper.ImageFormat.jpg] + } + ] }; // Act @@ -215,6 +224,19 @@ public async Task UpdateAppSettingsAsync_ValidInput_Success1() // Assert Assert.AreEqual(200, result.StatusCode); Assert.AreEqual("Updated", result.Message); + + var fileResultString2 = + await StreamToStringHelper.StreamToStringAsync( + storage.ReadStream(new AppSettings().AppSettingsPath)); + var fileResult2 = JsonSerializer.Deserialize(fileResultString2, + DefaultJsonSerializer.NoNamingPolicyBoolAsString); + + Assert.IsNotNull(fileResult2); + Assert.AreEqual(CollectionsOpenType.RawJpegMode.Raw, + fileResult2.App.DesktopCollectionsOpen); + Assert.AreEqual("/test", fileResult2.App.DefaultDesktopEditor[0].ApplicationPath); + Assert.AreEqual(ExtensionRolesHelper.ImageFormat.jpg, + fileResult2.App.DefaultDesktopEditor[0].ImageFormats[0]); } } } From ba1a2685d443e2a163ca731d68bc0f981b5badbb Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 26 Feb 2024 19:51:42 +0100 Subject: [PATCH 111/125] fix tests --- starsky/starskytest/FakeMocks/FakeIUserManger.cs | 5 +++++ starsky/starskytest/FakeMocks/FakeUserManagerActiveUsers.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/starsky/starskytest/FakeMocks/FakeIUserManger.cs b/starsky/starskytest/FakeMocks/FakeIUserManger.cs index 1bbaa1b0ae..771cb53227 100644 --- a/starsky/starskytest/FakeMocks/FakeIUserManger.cs +++ b/starsky/starskytest/FakeMocks/FakeIUserManger.cs @@ -115,6 +115,11 @@ public Role GetRole(string credentialTypeCode, string identifier) throw new System.NotImplementedException(); } + public Task GetRoleAsync(int userId) + { + throw new System.NotImplementedException(); + } + public bool PreflightValidate(string userName, string password, string confirmPassword) { diff --git a/starsky/starskytest/FakeMocks/FakeUserManagerActiveUsers.cs b/starsky/starskytest/FakeMocks/FakeUserManagerActiveUsers.cs index 779c9c178f..19cb1d6b18 100644 --- a/starsky/starskytest/FakeMocks/FakeUserManagerActiveUsers.cs +++ b/starsky/starskytest/FakeMocks/FakeUserManagerActiveUsers.cs @@ -154,6 +154,11 @@ public Task RemoveUser(string credentialTypeCode, return Role; } + public Task GetRoleAsync(int userId) + { + return Task.FromResult(Role); + } + public bool PreflightValidate(string userName, string password, string confirmPassword) { return password != "false"; From a77d6859f3d5bfe051ddf2c13b96fd6651c8a46e Mon Sep 17 00:00:00 2001 From: Dion Date: Mon, 26 Feb 2024 20:31:11 +0100 Subject: [PATCH 112/125] add keys --- .../preference-app-settings-desktop.tsx | 19 +- .../src/localization/localization.json | 301 ++++++++++++------ 2 files changed, 218 insertions(+), 102 deletions(-) diff --git a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx index c6c74d3d19..1ab8bccd9f 100644 --- a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx @@ -24,7 +24,7 @@ async function updateDefaultEditorPhotos( defaultDesktopEditor?: IAppSettingsDefaultEditorApplication[] ) { if (!defaultDesktopEditor) { - setIsMessage("FAIL"); + setIsMessage(MessageSwitchButtonDesktopCollectionsUpdateError); return; } const bodyParams = new URLSearchParams(); @@ -59,7 +59,9 @@ async function updateDefaultEditorPhotos( async function toggleCollections( value: boolean, - setIsMessage: React.Dispatch> + setIsMessage: React.Dispatch>, + MessageSwitchButtonDesktopCollectionsUpdateError: string, + MessageSwitchButtonDesktopCollectionsUpdateSuccess: string ) { const desktopCollectionsOpen = value ? RawJpegMode.Raw : RawJpegMode.Jpeg; @@ -68,10 +70,10 @@ async function toggleCollections( const result = await FetchPost(new UrlQuery().UrlApiAppSettings(), bodyParams.toString()); if (result.statusCode != 200) { - setIsMessage("FAIL"); + setIsMessage(MessageSwitchButtonDesktopCollectionsUpdateError); return; } - setIsMessage("OK"); + setIsMessage(MessageSwitchButtonDesktopCollectionsUpdateSuccess); } const PreferencesAppSettingsDesktop: React.FunctionComponent = () => { @@ -120,7 +122,14 @@ const PreferencesAppSettingsDesktop: React.FunctionComponent = () => { data-test="desktop-collections-open-toggle" isEnabled={appSettings?.useLocalDesktop && isAppSettingsWrite} leftLabel={language.key(localization.MessageSwitchButtonDesktopCollectionsJpegDefaultOff)} - onToggle={(value) => toggleCollections(value, setIsMessage)} + onToggle={(value) => + toggleCollections( + value, + setIsMessage, + language.key(localization.MessageSwitchButtonDesktopCollectionsRawJpegUpdateError), + language.key(localization.MessageSwitchButtonDesktopCollectionsRawJpegUpdateSuccess) + ) + } rightLabel={language.key(localization.MessageSwitchButtonDesktopCollectionsRawOn)} /> diff --git a/starsky/starsky/clientapp/src/localization/localization.json b/starsky/starsky/clientapp/src/localization/localization.json index 421f490574..9e33b83c4f 100644 --- a/starsky/starsky/clientapp/src/localization/localization.json +++ b/starsky/starsky/clientapp/src/localization/localization.json @@ -1,390 +1,497 @@ { "MessageMoveFolderIntoTrashIntroText": { "en": "Are you sure you want to move this folder into the trash?", - "nl": "Weet u zeker dat u deze map naar de prullenbak wilt verplaatsen?" + "nl": "Weet u zeker dat u deze map naar de prullenbak wilt verplaatsen?", + "de": "MΓΆchten Sie diesen Ordner wirklich in den Papierkorb verschieben?" }, "MessageCancel": { "en": "Cancel", - "nl": "Annuleren" + "nl": "Annuleren", + "de": "Abbrechen" }, "MessageTrash": { "en": "Trash", - "nl": "Prullenbak" + "nl": "Prullenbak", + "de": "Papierkorb" }, "MessageMoveToTrash": { "en": "Move to trash", - "nl": "Verplaatsen naar prullenbak" + "nl": "Verplaatsen naar prullenbak", + "de": "In den Papierkorb verschieben" }, "MessageMoveCurrentFolderToTrash": { "en": "Move current folder to trash", - "nl": "Huidige map naar prullenbak" + "nl": "Huidige map naar prullenbak", + "de": "Aktuellen Ordner in den Papierkorb verschieben" }, "MessageMove": { "en": "Move", - "nl": "Verplaatsen" + "nl": "Verplaatsen", + "de": "Verschieben" }, "MessageTo": { "en": "to", - "nl": "naar" + "nl": "naar", + "de": "in" }, "MessageRestoreFromTrash": { "en": "Restore from trash", - "nl": "Herstellen uit prullenbak" + "nl": "Herstellen uit prullenbak", + "de": "Aus dem Papierkorb wiederherstellen" }, "MessageSynchronizeManually": { "en": "Synchronize manually", - "nl": "Handmatig synchroniseren" + "nl": "Handmatig synchroniseren", + "de": "Manuell synchronisieren" }, "MessageDownload": { "en": "Download", - "nl": "Download" + "nl": "Download", + "de": "Herunterladen" }, "MessagePublish": { "en": "Publish", - "nl": "Publiceren" + "nl": "Publiceren", + "de": "VerΓΆffentlichen" }, "MessageReadOnlyFile": { "en": "Read only file", - "nl": "Alleen-lezen bestand" + "nl": "Alleen-lezen bestand", + "de": "SchreibgeschΓΌtzte Datei" }, "MessageNotFoundSourceMissing": { "en": "Not found. Source missing.", - "nl": "Niet gevonden. Bron ontbreekt." + "nl": "Niet gevonden. Bron ontbreekt.", + "de": "Nicht gefunden. Quelle fehlt." }, "MessageServerInputError": { "en": "Something is wrong with the input", - "nl": "Er is iets mis met de input" + "nl": "Er is iets mis met de input", + "de": "Etwas stimmt nicht mit der Eingabe" }, "MessageIsInTheTrash": { "en": "Is in the trash", - "nl": "Staat in de prullenbak" + "nl": "Staat in de prullenbak", + "de": "Befindet sich im Papierkorb" }, "MessageDeletedRestoreInstruction": { "en": "'Restore from Trash' to edit the item", - "nl": "'Zet terug uit prullenbak' om het item te bewerken" + "nl": "'Zet terug uit prullenbak' om het item te bewerken", + "de": "'Aus dem Papierkorb wiederherstellen', um das Element zu bearbeiten" }, "MessageHome": { "en": "Home", - "nl": "Home" + "nl": "Home", + "de": "Startseite" }, "MessagePhotosOfThisWeek": { "en": "Photos of this week", - "nl": "Foto's van deze week" + "nl": "Foto's van deze week", + "de": "Fotos dieser Woche" }, "MessageImport": { "en": "Import", - "nl": "Importeren" + "nl": "Importeren", + "de": "Import" }, "MessagePreferences": { "en": "Preferences", - "nl": "Voorkeuren" + "nl": "Voorkeuren", + "de": "Einstellungen" }, "MessageLogout": { "en": "Logout", - "nl": "Uitloggen" + "nl": "Uitloggen", + "de": "Ausloggen" }, "MessageSaved": { "en": "Saved", - "nl": "Opgeslagen" + "nl": "Opgeslagen", + "de": "Gespeichert" }, "MessageImportHeader": { "en": "Import", - "nl": "Importeren" + "nl": "Importeren", + "de": "Import" }, "MessageCloseDetailScreenDialog": { "en": "Close detail screen", - "nl": "Sluit detailscherm" + "nl": "Sluit detailscherm", + "de": "Detailbildschirm schließen" }, "MessageCloseDialogBackToFolder": { "en": "Parent folder", - "nl": "Terug naar map" + "nl": "Terug naar map", + "de": "Übergeordneter Ordner" }, "MessageIncludingColonWord": { "en": "Including: ", - "nl": "Inclusief: " + "nl": "Inclusief: ", + "de": "Inklusive: " }, "MessageAddLocation": { "en": "Add location", - "nl": "Voeg locatie toe" + "nl": "Voeg locatie toe", + "de": "Ort hinzufΓΌgen" }, "MessageUpdateLocation": { "en": "Update location", - "nl": "Werk locatie bij" + "nl": "Werk locatie bij", + "de": "Ort aktualisieren" }, "MessageViewLocation": { "en": "View location", - "nl": "Bekijk locatie" + "nl": "Bekijk locatie", + "de": "Ort anzeigen" }, "MessageErrorGenericFail": { "en": "Something went wrong with the update. Please try again", - "nl": "Er is iets misgegaan met het updaten. Probeer het opnieuw" + "nl": "Er is iets misgegaan met het updaten. Probeer het opnieuw", + "de": "Bei der Aktualisierung ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut" }, "MessageNoPhotosInFolder": { "en": "There are no photos in this folder", - "nl": "Er zijn geen foto's in deze map" + "nl": "Er zijn geen foto's in deze map", + "de": "In diesem Ordner befinden sich keine Fotos" }, "MessageNewUserNoPhotosInFolder": { "en": "New? Set your drive location in the settings.", - "nl": "Nieuw? Stel je schijflocatie in de instellingen." + "nl": "Nieuw? Stel je schijflocatie in de instellingen.", + "de": "Neu? Legen Sie Ihren Laufwerksspeicherort in den Einstellungen fest." }, "MessageItemsOutsideFilter": { "en": "There are more items, but these are outside of your filters. To see everything click on 'Reset Filter'", - "nl": "Er zijn meer items, maar deze vallen buiten je filters. Om alles te zien klik op 'Herstel Filter'" + "nl": "Er zijn meer items, maar deze vallen buiten je filters. Om alles te zien klik op 'Herstel Filter'", + "de": "Es gibt mehr Elemente, aber diese befinden sich außerhalb Ihrer Filter. Um alles zu sehen, klicken Sie auf 'Filter zurΓΌcksetzen'" }, "MessageSelectPresentPerfect": { "en": "selected", - "nl": "geselecteerd" + "nl": "geselecteerd", + "de": "ausgewΓ€hlt" }, "MessageSelectAction": { "en": "Select", - "nl": "Selecteer" + "nl": "Selecteer", + "de": "AuswΓ€hlen" }, "MessageNoneSelected": { "en": "Nothing selected", - "nl": "Niets geselecteerd" + "nl": "Niets geselecteerd", + "de": "Nichts ausgewΓ€hlt" }, "MessageUndoSelection": { "en": "Undo selection", - "nl": "Undo selectie" + "nl": "Undo selectie", + "de": "Auswahl rΓΌckgΓ€ngig machen" }, "MessageSelectAll": { "en": "Select all", - "nl": "Alles selecteren" + "nl": "Alles selecteren", + "de": "Alles auswΓ€hlen" }, "MessageSelectFurther": { "en": "Select further", - "nl": "Verder selecteren" + "nl": "Verder selecteren", + "de": "Weiter auswΓ€hlen" }, "MessageGoToParentFolder": { "en": "Go to parent folder", - "nl": "Ga naar bovenliggende map" + "nl": "Ga naar bovenliggende map", + "de": "Zum ΓΌbergeordneten Ordner gehen" }, "MessageMkdir": { "en": "Create folder", - "nl": "Map maken" + "nl": "Map maken", + "de": "Ordner erstellen" }, "MessageDisplayOptions": { "en": "Display options", - "nl": "Weergave opties" + "nl": "Weergave opties", + "de": "Anzeigeoptionen" }, "MessageRenameDir": { "en": "Rename", - "nl": "Naam wijzigen" + "nl": "Naam wijzigen", + "de": "Umbenennen" }, "MessageLabels": { "en": "Labels", - "nl": "Labels" + "nl": "Labels", + "de": "Etiketten" }, "MessageRenameFileName": { "en": "Rename file name", - "nl": "Bestandsnaam wijzigen" + "nl": "Bestandsnaam wijzigen", + "de": "Dateinamen umbenennen" }, "MessageRotateToRight": { "en": "Rotation to the right", - "nl": "Rotatie naar rechts" + "nl": "Rotatie naar rechts", + "de": "Drehung nach rechts" }, "MessageDeleteImmediately": { "en": "Delete immediately", - "nl": "Verwijder onmiddellijk" + "nl": "Verwijder onmiddellijk", + "de": "Unmittelbar lΓΆschen" }, "MessageVideoPlayBackError": { "en": "There is something wrong with the playback of this video. Try 'More' and 'Download'. ", - "nl": "Er is iets mis met het afspelen van deze video. Probeer eens via het menu 'Meer' en 'Download'." + "nl": "Er is iets mis met het afspelen van deze video. Probeer eens via het menu 'Meer' en 'Download'.", + "de": "Beim Abspielen dieses Videos ist etwas schiefgelaufen. Versuchen Sie 'Weitere' und 'Download'." }, "MessageVideoNotFound": { "en": "This video is not found", - "nl": "Deze video is niet gevonden" + "nl": "Deze video is niet gevonden", + "de": "Dieses Video wurde nicht gefunden" }, "ColorClassColour0": { "en": "Colorless", - "nl": "Kleurloos" + "nl": "Kleurloos", + "de": "Farblos" }, "ColorClassColour1": { "en": "Pink", - "nl": "Roze" + "nl": "Roze", + "de": "Rosa" }, "ColorClassColour2": { "en": "Red", - "nl": "Rood" + "nl": "Rood", + "de": "Rot" }, "ColorClassColour3": { "en": "Orange", - "nl": "Oranje" + "nl": "Oranje", + "de": "Orange" }, "ColorClassColour4": { "en": "Yellow", - "nl": "Geel" + "nl": "Geel", + "de": "Gelb" }, "ColorClassColour5": { "en": "Green", - "nl": "Groen" + "nl": "Groen", + "de": "GrΓΌn" }, "ColorClassColour6": { "en": "Azure", - "nl": "Azuur" + "nl": "Azuur", + "de": "Azurblau" }, "ColorClassColour7": { "en": "Blue", - "nl": "Blauw" + "nl": "Blauw", + "de": "Blau" }, "ColorClassColour8": { "en": "Grey", - "nl": "Grijs" + "nl": "Grijs", + "de": "Grau" }, "ColorClassColourResetFilter": { "en": "Reset filter", - "nl": "Herstel filter" + "nl": "Herstel filter", + "de": "Filter zurΓΌcksetzen" }, "MessageCloseDialog": { "en": "Close", - "nl": "Sluiten" + "nl": "Sluiten", + "de": "Schließen" }, "MessageDesktopEditorOpenSingleFile": { "en": "Open file", - "nl": "Open bestand" + "nl": "Open bestand", + "de": "Datei ΓΆffnen" }, "MessageDesktopEditorOpenMultipleFiles": { "en": "Open files", - "nl": "Open bestanden" + "nl": "Open bestanden", + "de": "Dateien ΓΆffnen" }, "MessageDesktopEditorUnableToOpen": { "en": "Sorry, something went wrong opening the file", - "nl": "Sorry, er iets misgegaan met het openen van het bestand" + "nl": "Sorry, er iets misgegaan met het openen van het bestand", + "de": "Entschuldigung, beim Γ–ffnen der Datei ist ein Fehler aufgetreten" }, "MessageDesktopEditorConfirmationHeading": { "en": "Do you really want to edit all of the selected photos?", - "nl": "Wil je echt alle geselecteerde foto's bewerken?" + "nl": "Wil je echt alle geselecteerde foto's bewerken?", + "de": "MΓΆchten Sie wirklich alle ausgewΓ€hlten Fotos bearbeiten?" }, "MessageDesktopEditorConfirmationIntroText": { "en": "This prompt is to prevent potential issues such as your computer freezing", - "nl": "Deze melding is bedoeld om te voorkomen dat er eventuele problemen optreden, zoals het vastlopen van je computer." + "nl": "Deze melding is bedoeld om te voorkomen dat er eventuele problemen optreden, zoals het vastlopen van je computer.", + "de": "Dieser Hinweis soll potenzielle Probleme wie das Einfrieren Ihres Computers verhindern" }, "MessageItemSelectionRequired": { "en": "Please select at least one item before proceeding.", - "nl": "Selecteer alstublieft eerst minstens één item voordat u doorgaat." + "nl": "Selecteer alstublieft eerst minstens één item voordat u doorgaat.", + "de": "Bitte wΓ€hlen Sie mindestens einen Artikel aus, bevor Sie fortfahren." }, "MessageAppSettingsEntireAppScope": { "en": "The AppSettings may only be modified by Administrators. These settings are applied for the entire application.", - "nl": "De AppSettings mogen alleen worden aangepast door Administrators. Deze instellingen worden toegepast voor de gehele applicatie." + "nl": "De AppSettings mogen alleen worden aangepast door Administrators. Deze instellingen worden toegepast voor de gehele applicatie.", + "de": "Die AppSettings dΓΌrfen nur von Administratoren geΓ€ndert werden. Diese Einstellungen gelten fΓΌr die gesamte Anwendung." }, "MessageChangeNeedReSync": { "en": "You have changed this setting, now you need to perform a full sync. Go to the root folder, the more menu and click on manual sync.", - "nl": "Je hebt deze instelling veranderd, nu dien je een volledige sync uit te voeren. Ga naar de hoofdmap, het meer-menu en klik op handmatig synchroniseren." + "nl": "Je hebt deze instelling veranderd, nu dien je een volledige sync uit te voeren. Ga naar de hoofdmap, het meer-menu en klik op handmatig synchroniseren.", + "de": "Sie haben diese Einstellung geΓ€ndert, jetzt mΓΌssen Sie eine vollstΓ€ndige Synchronisierung durchfΓΌhren. Gehen Sie zum Stammverzeichnis, zum MenΓΌ 'Weitere Optionen' und klicken Sie auf manuelle Synchronisierung." }, "MessageAppSettingsStorageFolder": { "en": "Storage Folder", - "nl": "Opslag map" + "nl": "Opslag map", + "de": "Speicherordner" }, "MessageAppSettingStorageFolderSaveFail": { "en": "Apologies, unable to save, we couldn't find the directory", - "nl": "Sorry het opslaan van de map is niet gelukt, de map is niet gevonden" + "nl": "Sorry het opslaan van de map is niet gelukt, de map is niet gevonden", + "de": "Entschuldigung, Speichern nicht mΓΆglich, Verzeichnis konnte nicht gefunden werden" }, "MessageAppSettingStorageFolderEnvUsedFail": { "en": "Please update the 'app__storageFolder' environment variable to change the location", - "nl": "Update alsjeblieft de 'app__storageFolder' environment variable om de opslag locatie te veranderen" + "nl": "Update alsjeblieft de 'app__storageFolder' environment variable om de opslag locatie te veranderen", + "de": "Bitte aktualisieren Sie die Umgebungsvariable 'app__storageFolder', um den Speicherort zu Γ€ndern" }, "MessageSwitchButtonDesktopCollectionsRawOn": { "en": "Raw first", - "nl": "Raw eerst" + "nl": "Raw eerst", + "de": "Raw zuerst" }, "MessageSwitchButtonDesktopCollectionsJpegDefaultOff": { "en": "Jpeg first", - "nl": "Jpeg eerst" + "nl": "Jpeg eerst", + "de": "Jpeg zuerst" }, "MessageSwitchButtonDesktopApplication": { "en": "AppSettings: Desktop application", - "nl": "AppSettings: Desktop applicatie" + "nl": "AppSettings: Desktop applicatie", + "de": "App-Einstellungen: Desktop-Anwendung" }, "MessageSwitchButtonDesktopApplicationDescription": { "en": "These settings are only available when using as Desktop application", - "nl": "Deze instellingen zijn alleen beschikbaar als desktop applicatie" + "nl": "Deze instellingen zijn alleen beschikbaar als desktop applicatie", + "de": "Diese Einstellungen sind nur verfΓΌgbar, wenn sie als Desktop-Anwendung verwendet werden" }, "MessageAppSettingDefaultEditorPhotos": { "en": "Default application to edit photos:", - "nl": "Standaardtoepassing om foto's te bewerken:" + "nl": "Standaardtoepassing om foto's te bewerken:", + "de": "Standardanwendung zum Bearbeiten von Fotos:" }, "MessageAppSettingDefaultEditorPhotosDescription": { "en": "Keep emthy to use the default system application ", - "nl": "Hou leeg om de standaardtoepassing van het systeem te gebruiken" + "nl": "Hou leeg om de standaardtoepassing van het systeem te gebruiken", + "de": "Leer lassen, um die Standard-Systemanwendung zu verwenden" }, "MessageSwitchButtonDesktopCollectionsUpdateError": { "en": "Something went wrong updating the collection preferences", - "nl": "Er ging iets met het updaten van de collection voorkeur " + "nl": "Er ging iets met het updaten van de collectie voorkeur ", + "de": "Beim Aktualisieren der Sammlungseinstellungen ist ein Fehler aufgetreten" }, "MessageSwitchButtonDesktopCollectionsUpdateSuccess": { "en": "The collection preference is updated", - "nl": "De collection voorkeur is bijgewerkt" + "nl": "De collection voorkeur is bijgewerkt", + "de": "Die Sammlungseinstellung wurde aktualisiert" + }, + "MessageSwitchButtonDesktopCollectionsRawJpegUpdateError": { + "en": "Something went wrong updating the Raw / Jpeg preferences", + "nl": "Er ging iets mis bij het bijwerken van de voorkeur voor Raw / Jpeg", + "de": "Beim Aktualisieren der Raw / Jpeg-Einstellungen ist ein Fehler aufgetreten" + }, + "MessageSwitchButtonDesktopCollectionsRawJpegUpdateSuccess": { + "en": "The Raw / Jpeg preference is updated", + "nl": "De voorkeur voor Raw / Jpeg is bijgewerkt", + "de": "Die Raw / Jpeg-Einstellung wurde aktualisiert" }, "MessageUnknownUsername": { "en": "Unknown username", - "nl": "Onbekende gebruikersnaam" + "nl": "Onbekende gebruikersnaam", + "de": "Unbekannter Benutzername" }, "MessageUsername": { "en": "Username", - "nl": "Gebruikersnaam" + "nl": "Gebruikersnaam", + "de": "Benutzername" }, "MessageRole": { "en": "Role", - "nl": "Rol" + "nl": "Rol", + "de": "Rolle" }, "MessageExamplePassword": { "en": "supersafe", - "nl": "superveilig" + "nl": "superveilig", + "de": "supersicher" }, "MessageCurrentPassword": { "en": "Enter your current password", - "nl": "Geef je huidige wachtwoord op" + "nl": "Voer uw huidige wachtwoord in", + "de": "Geben Sie Ihr aktuelles Passwort ein" }, "MessageChangedPassword": { "en": "Enter your new password", - "nl": "Geef je nieuwe wachtwoord op" + "nl": "Voer uw nieuwe wachtwoord in", + "de": "Geben Sie Ihr neues Passwort ein" }, "MessageChangedConfirmPassword": { "en": "And your new password again", - "nl": "Herhaal je nieuwe wachtwoord" + "nl": "Herhaal je nieuwe wachtwoord", + "de": "Und Ihr neues Passwort erneut" }, "MessageNoPassword": { "en": "Enter the current and new password", - "nl": "Voer het huidige en nieuwe wachtwoord in" + "nl": "Voer het huidige en nieuwe wachtwoord in", + "de": "Geben Sie das aktuelle und das neue Passwort ein" }, "MessagePassword": { "en": "Enter your password", - "nl": "Geef je wachtwoord op" + "nl": "Geef je wachtwoord op", + "de": "Geben Sie Ihr Passwort ein" }, "MessagePasswordChanged": { "en": "Your password has been successfully changed", - "nl": "Je wachtwoord is succesvol veranderd" + "nl": "Je wachtwoord is succesvol veranderd", + "de": "Ihr Passwort wurde erfolgreich geΓ€ndert" }, "MessagePasswordNoMatch": { "en": "The passwords do not match", - "nl": "De wachtwoorden komen niet overeen" + "nl": "De wachtwoorden komen niet overeen", + "de": "Die PasswΓΆrter stimmen nicht ΓΌberein" }, "MessagePasswordModalError": { "en": "The new password does not meet the criteria", - "nl": "Het nieuwe wachtwoord voldoet niet aan de criteria" + "nl": "Het nieuwe wachtwoord voldoet niet aan de criteria", + "de": "Das neue Passwort entspricht nicht den Kriterien" }, "MessageCreateNewAccount": { + "en": "Create new account", "nl": "Maak nieuw account", - "en": "Create new account" + "de": "Neues Konto erstellen" }, "MessageExampleUsername": { "en": "dont@mail.me", - "nl": "dont@mail.me" + "nl": "dont@mail.me", + "de": "dont@mail.me" }, "MessageConfirmPassword": { "en": "Enter your password again", - "nl": "Vul je wachtwoord nog een keer in" + "nl": "Vul je wachtwoord nog een keer in", + "de": "Geben Sie Ihr Passwort erneut ein" }, "MessageNoUsernamePassword": { "en": "Enter an email address and password", - "nl": "Voer een emailadres en een wachtwoord in" + "nl": "Voer een emailadres en een wachtwoord in", + "de": "Geben Sie eine E-Mail-Adresse und ein Passwort ein" }, "MessageWrongFormatEmailAddress": { "en": "Check your email address", - "nl": "Controleer je email adres" + "nl": "Controleer je email adres", + "de": "ÜberprΓΌfen Sie Ihre E-Mail-Adresse" }, "temp1": { "en": "", - "nl": "" + "nl": "", + "de": "" } } From d240e7019015ade635841634fd355967de419195 Mon Sep 17 00:00:00 2001 From: Dion Date: Tue, 27 Feb 2024 21:36:40 +0100 Subject: [PATCH 113/125] add usings --- .../preference-app-settings-desktop.tsx | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx index 1ab8bccd9f..fbcb1987a0 100644 --- a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx @@ -16,7 +16,7 @@ const defaultEditorApplication = { imageFormats: [ImageFormat.jpg, ImageFormat.png, ImageFormat.bmp, ImageFormat.tiff] } as IAppSettingsDefaultEditorApplication; -async function updateDefaultEditorPhotos( +export async function UpdateDefaultEditorPhotos( event: ChangeEvent, setIsMessage: React.Dispatch>, MessageSwitchButtonDesktopCollectionsUpdateError: string, @@ -57,11 +57,12 @@ async function updateDefaultEditorPhotos( setIsMessage(MessageSwitchButtonDesktopCollectionsUpdateSuccess); } -async function toggleCollections( +export async function ToggleCollections( value: boolean, setIsMessage: React.Dispatch>, MessageSwitchButtonDesktopCollectionsUpdateError: string, - MessageSwitchButtonDesktopCollectionsUpdateSuccess: string + MessageSwitchButtonDesktopCollectionsUpdateSuccess: string, + appSettings: IAppSettings | null ) { const desktopCollectionsOpen = value ? RawJpegMode.Raw : RawJpegMode.Jpeg; @@ -69,10 +70,12 @@ async function toggleCollections( bodyParams.set("desktopCollectionsOpen", desktopCollectionsOpen.toString()); const result = await FetchPost(new UrlQuery().UrlApiAppSettings(), bodyParams.toString()); - if (result.statusCode != 200) { + if (result.statusCode != 200 || !appSettings) { setIsMessage(MessageSwitchButtonDesktopCollectionsUpdateError); return; } + // to avoid re-render issues to display message + appSettings.desktopCollectionsOpen = desktopCollectionsOpen; setIsMessage(MessageSwitchButtonDesktopCollectionsUpdateSuccess); } @@ -123,22 +126,24 @@ const PreferencesAppSettingsDesktop: React.FunctionComponent = () => { isEnabled={appSettings?.useLocalDesktop && isAppSettingsWrite} leftLabel={language.key(localization.MessageSwitchButtonDesktopCollectionsJpegDefaultOff)} onToggle={(value) => - toggleCollections( + ToggleCollections( value, setIsMessage, language.key(localization.MessageSwitchButtonDesktopCollectionsRawJpegUpdateError), - language.key(localization.MessageSwitchButtonDesktopCollectionsRawJpegUpdateSuccess) + language.key(localization.MessageSwitchButtonDesktopCollectionsRawJpegUpdateSuccess), + appSettings ) } rightLabel={language.key(localization.MessageSwitchButtonDesktopCollectionsRawOn)} /> - +
+

{language.key(localization.MessageAppSettingDefaultEditorPhotos)}

{language.key(localization.MessageAppSettingDefaultEditorPhotosDescription)}

- updateDefaultEditorPhotos( + UpdateDefaultEditorPhotos( value, setIsMessage, language.key(localization.MessageSwitchButtonDesktopCollectionsUpdateError), From 9fdf5cefe363a8fd5dbba30d0c91d360d87c0a08 Mon Sep 17 00:00:00 2001 From: Dion Date: Tue, 27 Feb 2024 21:50:17 +0100 Subject: [PATCH 114/125] add tests --- .../preference-app-settings-desktop.spec.tsx | 138 ++++++++++++++++++ .../preference-app-settings-desktop.tsx | 15 +- 2 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.spec.tsx diff --git a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.spec.tsx b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.spec.tsx new file mode 100644 index 0000000000..58f5943f4c --- /dev/null +++ b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.spec.tsx @@ -0,0 +1,138 @@ +import { render, screen } from "@testing-library/react"; +import { ChangeEvent } from "react"; +import * as useFetch from "../../../hooks/use-fetch"; +import { IAppSettings } from "../../../interfaces/IAppSettings"; +import { RawJpegMode } from "../../../interfaces/ICollectionsOpenType"; +import { IConnectionDefault } from "../../../interfaces/IConnectionDefault"; +import { ImageFormat } from "../../../interfaces/IFileIndexItem"; +import * as FetchPost from "../../../shared/fetch/fetch-post"; +import * as SwitchButton from "../../atoms/switch-button/switch-button"; +import PreferencesAppSettingsDesktop, { + ToggleCollections, + UpdateDefaultEditorPhotos +} from "./preference-app-settings-desktop"; + +describe("PreferencesAppSettingsDesktop", () => { + it("should render correctly with provided props", () => { + const switchButtonSpy = jest.spyOn(SwitchButton, "default").mockImplementationOnce(() => { + return <>; + }); + + render(); + + expect(switchButtonSpy).toHaveBeenCalled(); + }); + + it("should render MessageSwitchButtonDesktopApplicationDescription when appSettings.useLocalDesktop is true", () => { + const mockGetIConnectionDefaultAppSettings = { + statusCode: 200, + data: { + useLocalDesktop: true, + defaultDesktopEditor: [] + } + } as IConnectionDefault; + + const mockGetIConnectionDefaultPermissions = { + statusCode: 200, + data: null + } as IConnectionDefault; + const useFetchSpy = jest + .spyOn(useFetch, "default") + .mockReset() + .mockImplementationOnce(() => mockGetIConnectionDefaultAppSettings) + .mockImplementationOnce(() => mockGetIConnectionDefaultPermissions); + + const component = render(); + + expect( + screen.findByTestId("preference-app-settings-desktop-use-local-desktop-true") + ).toBeTruthy(); + + expect(useFetchSpy).toHaveBeenCalled(); + expect(useFetchSpy).toHaveBeenCalledTimes(2); + + component.unmount(); + }); + + it("should render MessageSwitchButtonDesktopApplicationDescription when appSettings.useLocalDesktop is false", () => { + const mockGetIConnectionDefaultAppSettings = { + statusCode: 200, + data: { + useLocalDesktop: false, + defaultDesktopEditor: [] + } + } as IConnectionDefault; + + const mockGetIConnectionDefaultPermissions = { + statusCode: 200, + data: null + } as IConnectionDefault; + const useFetchSpy = jest + .spyOn(useFetch, "default") + .mockReset() + .mockImplementationOnce(() => mockGetIConnectionDefaultAppSettings) + .mockImplementationOnce(() => mockGetIConnectionDefaultPermissions); + + const component = render(); + + expect( + screen.findByTestId("preference-app-settings-desktop-use-local-desktop-false") + ).toBeTruthy(); + + expect(useFetchSpy).toHaveBeenCalled(); + expect(useFetchSpy).toHaveBeenCalledTimes(2); + + component.unmount(); + }); +}); + +describe("FetchPost Function", () => { + it("should call FetchPost with correct URL and bodyParams for updateDefaultEditorPhotos function", async () => { + const value = { + target: { innerText: "NewApplicationPath" } + } as unknown as ChangeEvent; + const defaultDesktopEditor = [ + { + applicationPath: "SampleApplicationPath", + imageFormats: [ImageFormat.jpg, ImageFormat.png] + } + ]; + const mockIConnectionDefault: Promise = Promise.resolve({ + data: null, + statusCode: 200 + }); + + const spyFetchPost = jest + .spyOn(FetchPost, "default") + .mockImplementationOnce(() => mockIConnectionDefault); + + await UpdateDefaultEditorPhotos(value, jest.fn(), "", "", defaultDesktopEditor); + + expect(spyFetchPost).toHaveBeenCalledWith( + expect.stringContaining("api/env"), + expect.any(String) + ); + }); + + it("should call FetchPost with correct URL and bodyParams for toggleCollections function", async () => { + const mockIConnectionDefault: Promise = Promise.resolve({ + data: null, + statusCode: 200 + }); + + const spyFetchPost = jest + .spyOn(FetchPost, "default") + .mockImplementationOnce(() => mockIConnectionDefault); + const appSettings = { + desktopCollectionsOpen: RawJpegMode.Default, + useLocalDesktop: true + } as unknown as IAppSettings; + + await ToggleCollections(true, jest.fn(), "", "", appSettings); + + expect(spyFetchPost).toHaveBeenCalledWith( + expect.stringContaining("api/env"), + expect.any(String) + ); + }); +}); diff --git a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx index fbcb1987a0..ac6972390e 100644 --- a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx @@ -111,11 +111,16 @@ const PreferencesAppSettingsDesktop: React.FunctionComponent = () => { <>
{MessageSwitchButtonDesktopApplication}
- {appSettings?.useLocalDesktop ? ( -

{MessageSwitchButtonDesktopApplicationDescription}

- ) : ( -
{MessageSwitchButtonDesktopApplicationDescription}
- )} +
+ {MessageSwitchButtonDesktopApplicationDescription} +
{isMessage !== "" ? (
{isMessage}
From 3b710ab2b87e5c03ed1ab23f8d3c720ab4780cd4 Mon Sep 17 00:00:00 2001 From: Dion Date: Tue, 27 Feb 2024 22:12:09 +0100 Subject: [PATCH 115/125] tests --- .../preference-app-settings-desktop.spec.tsx | 148 +++++++++++++++++- .../preference-app-settings-desktop.tsx | 7 +- 2 files changed, 150 insertions(+), 5 deletions(-) diff --git a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.spec.tsx b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.spec.tsx index 58f5943f4c..c9f1a7bcba 100644 --- a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.spec.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { ChangeEvent } from "react"; import * as useFetch from "../../../hooks/use-fetch"; import { IAppSettings } from "../../../interfaces/IAppSettings"; @@ -6,6 +6,8 @@ import { RawJpegMode } from "../../../interfaces/ICollectionsOpenType"; import { IConnectionDefault } from "../../../interfaces/IConnectionDefault"; import { ImageFormat } from "../../../interfaces/IFileIndexItem"; import * as FetchPost from "../../../shared/fetch/fetch-post"; +import { UrlQuery } from "../../../shared/url-query"; +import * as FormControl from "../../atoms/form-control/form-control"; import * as SwitchButton from "../../atoms/switch-button/switch-button"; import PreferencesAppSettingsDesktop, { ToggleCollections, @@ -84,9 +86,145 @@ describe("PreferencesAppSettingsDesktop", () => { component.unmount(); }); + + it("give message when done with SwitchButton", async () => { + const mockGetIConnectionDefaultAppSettings = { + statusCode: 200, + data: { + useLocalDesktop: true, + defaultDesktopEditor: [] + } + } as IConnectionDefault; + + const mockGetIConnectionDefaultPermissions = { + statusCode: 200, + data: null + } as IConnectionDefault; + const useFetchSpy = jest + .spyOn(useFetch, "default") + .mockReset() + .mockImplementationOnce(() => mockGetIConnectionDefaultAppSettings) + .mockImplementationOnce(() => mockGetIConnectionDefaultPermissions) + .mockImplementationOnce(() => mockGetIConnectionDefaultAppSettings) + .mockImplementationOnce(() => mockGetIConnectionDefaultPermissions) + .mockImplementationOnce(() => mockGetIConnectionDefaultAppSettings) + .mockImplementationOnce(() => mockGetIConnectionDefaultPermissions); + + const mockIConnectionDefault: Promise = Promise.resolve({ + data: null, + statusCode: 200 + }); + + const spyFetchPost = jest + .spyOn(FetchPost, "default") + .mockReset() + .mockImplementationOnce(() => mockIConnectionDefault); + + const switchButtonSpy = jest.spyOn(SwitchButton, "default").mockImplementationOnce((props) => { + return ; + }); + + const component = render(); + + expect(screen.queryByTestId("preference-app-settings-desktop-warning-box")).toBeFalsy(); + + fireEvent.click(screen.getByTestId("switch-button-spy")); + + await waitFor(() => { + expect(spyFetchPost).toHaveBeenCalled(); + expect(spyFetchPost).toHaveBeenCalledTimes(1); + expect(spyFetchPost).toHaveBeenCalledWith( + new UrlQuery().UrlApiAppSettings(), + "desktopCollectionsOpen=2" + ); + + expect(screen.getByTestId("preference-app-settings-desktop-warning-box")).toBeTruthy(); + }); + + expect(switchButtonSpy).toHaveBeenCalled(); + + expect(useFetchSpy).toHaveBeenCalled(); + expect(useFetchSpy).toHaveBeenCalledTimes(6); + + component.unmount(); + }); + + it("give message when done with FormControl", async () => { + const mockGetIConnectionDefaultAppSettings = { + statusCode: 200, + data: { + useLocalDesktop: true, + defaultDesktopEditor: [] + } + } as IConnectionDefault; + + const mockGetIConnectionDefaultPermissions = { + statusCode: 200, + data: null + } as IConnectionDefault; + const useFetchSpy = jest + .spyOn(useFetch, "default") + .mockReset() + .mockImplementationOnce(() => mockGetIConnectionDefaultAppSettings) + .mockImplementationOnce(() => mockGetIConnectionDefaultPermissions) + .mockImplementationOnce(() => mockGetIConnectionDefaultAppSettings) + .mockImplementationOnce(() => mockGetIConnectionDefaultPermissions); + + const mockIConnectionDefault: Promise = Promise.resolve({ + data: null, + statusCode: 200 + }); + + const spyFetchPost = jest + .spyOn(FetchPost, "default") + .mockReset() + .mockImplementationOnce(() => mockIConnectionDefault); + + const switchButtonSpy = jest.spyOn(FormControl, "default").mockImplementationOnce((props) => { + return ( + + ); + }); + + const component = render(); + + expect(screen.queryByTestId("preference-app-settings-desktop-warning-box")).toBeFalsy(); + + fireEvent.click(screen.getByTestId("form-control-spy")); + + await waitFor(() => { + const query = + "DefaultDesktopEditor%5B0%5D.ImageFormats%5B0%5D=jpg&DefaultDesktopEditor%5B0%5D.ImageFormats%" + + "5B1%5D=png&DefaultDesktopEditor%5B0%5D.ImageFormats%5B2%5D=bmp&DefaultDesktopEditor%5B0%5D.ImageFormats%5B3%5D=tiff&" + + "DefaultDesktopEditor%5B0%5D.ApplicationPath=test"; + + expect(spyFetchPost).toHaveBeenCalledTimes(1); + expect(spyFetchPost).toHaveBeenCalledWith(new UrlQuery().UrlApiAppSettings(), query); + + expect(screen.getByTestId("preference-app-settings-desktop-warning-box")).toBeTruthy(); + }); + + expect(switchButtonSpy).toHaveBeenCalled(); + + expect(useFetchSpy).toHaveBeenCalled(); + expect(useFetchSpy).toHaveBeenCalledTimes(4); + + component.unmount(); + }); }); -describe("FetchPost Function", () => { +describe("updateDefaultEditorPhotos", () => { it("should call FetchPost with correct URL and bodyParams for updateDefaultEditorPhotos function", async () => { const value = { target: { innerText: "NewApplicationPath" } @@ -109,11 +247,13 @@ describe("FetchPost Function", () => { await UpdateDefaultEditorPhotos(value, jest.fn(), "", "", defaultDesktopEditor); expect(spyFetchPost).toHaveBeenCalledWith( - expect.stringContaining("api/env"), + expect.stringContaining(new UrlQuery().UrlApiAppSettings()), expect.any(String) ); }); +}); +describe("ToggleCollections", () => { it("should call FetchPost with correct URL and bodyParams for toggleCollections function", async () => { const mockIConnectionDefault: Promise = Promise.resolve({ data: null, @@ -131,7 +271,7 @@ describe("FetchPost Function", () => { await ToggleCollections(true, jest.fn(), "", "", appSettings); expect(spyFetchPost).toHaveBeenCalledWith( - expect.stringContaining("api/env"), + expect.stringContaining(new UrlQuery().UrlApiAppSettings()), expect.any(String) ); }); diff --git a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx index ac6972390e..3cf849a4ec 100644 --- a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.tsx @@ -123,7 +123,12 @@ const PreferencesAppSettingsDesktop: React.FunctionComponent = () => {
{isMessage !== "" ? ( -
{isMessage}
+
+ {isMessage} +
) : null} Date: Tue, 27 Feb 2024 22:28:25 +0100 Subject: [PATCH 116/125] add test code --- .../preference-app-settings-desktop.spec.tsx | 198 +++++++++++++++++- 1 file changed, 195 insertions(+), 3 deletions(-) diff --git a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.spec.tsx b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.spec.tsx index c9f1a7bcba..ab2cc29049 100644 --- a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.spec.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.spec.tsx @@ -87,6 +87,54 @@ describe("PreferencesAppSettingsDesktop", () => { component.unmount(); }); + it("should render MessageSwitchButtonDesktopApplicationDescription when appSettings.useLocalDesktop is true", () => { + const mockGetIConnectionDefaultAppSettings = { + statusCode: 200, + data: { + useLocalDesktop: true, + defaultDesktopEditor: [ + { + applicationPath: "/test", + imageFormats: [ImageFormat.tiff] + } + ] + } as unknown as IAppSettings + } as IConnectionDefault; + + const formControlSpy = jest.spyOn(FormControl, "default").mockImplementationOnce(() => { + return <>; + }); + + const mockGetIConnectionDefaultPermissions = { + statusCode: 200, + data: null + } as IConnectionDefault; + const useFetchSpy = jest + .spyOn(useFetch, "default") + .mockReset() + .mockImplementationOnce(() => mockGetIConnectionDefaultAppSettings) + .mockImplementationOnce(() => mockGetIConnectionDefaultPermissions); + + const component = render(); + + expect(formControlSpy).toHaveBeenCalled(); + expect(formControlSpy).toHaveBeenCalledWith( + { + children: "/test", + contentEditable: undefined, + name: "tags", + onBlur: expect.anything(), + spellcheck: true + }, + {} + ); + + expect(useFetchSpy).toHaveBeenCalled(); + expect(useFetchSpy).toHaveBeenCalledTimes(2); + + component.unmount(); + }); + it("give message when done with SwitchButton", async () => { const mockGetIConnectionDefaultAppSettings = { statusCode: 200, @@ -251,10 +299,106 @@ describe("updateDefaultEditorPhotos", () => { expect.any(String) ); }); + + it("UpdateDefaultEditorPhotos call error if default defaultDesktopEditor is missing", async () => { + const value = { + target: { innerText: "NewApplicationPath" } + } as unknown as ChangeEvent; + const setIsMessage = jest.fn(); + await UpdateDefaultEditorPhotos(value, setIsMessage, "error_here", ""); + expect(setIsMessage).toHaveBeenCalled(); + expect(setIsMessage).toHaveBeenCalledWith("error_here"); + }); + + it("UpdateDefaultEditorPhotos call error if fetchPost is Error 500", async () => { + const value = { + target: { innerText: "NewApplicationPath" } + } as unknown as ChangeEvent; + + const mockIConnectionDefault: Promise = Promise.resolve({ + data: null, + statusCode: 500 + }); + + const spyFetchPost = jest + .spyOn(FetchPost, "default") + .mockImplementationOnce(() => mockIConnectionDefault); + + const setIsMessage = jest.fn(); + await UpdateDefaultEditorPhotos(value, setIsMessage, "error_here", "", []); + expect(setIsMessage).toHaveBeenCalled(); + expect(setIsMessage).toHaveBeenCalledWith("error_here"); + + expect(spyFetchPost).toHaveBeenCalled(); + }); + + it("Create new item in Array if emthy array", async () => { + const value = { + target: { innerText: "test" } + } as unknown as ChangeEvent; + + const mockIConnectionDefault: Promise = Promise.resolve({ + data: null, + statusCode: 200 + }); + + const spyFetchPost = jest + .spyOn(FetchPost, "default") + .mockReset() + .mockImplementationOnce(() => mockIConnectionDefault); + + const setIsMessage = jest.fn(); + await UpdateDefaultEditorPhotos(value, setIsMessage, "error_here", "success", []); + expect(setIsMessage).toHaveBeenCalled(); + expect(setIsMessage).toHaveBeenCalledWith("success"); + + expect(spyFetchPost).toHaveBeenCalled(); + const query = + "DefaultDesktopEditor%5B0%5D.ImageFormats%5B0%5D=jpg&DefaultDesktopEditor%5B0%5D.ImageFormats%" + + "5B1%5D=png&DefaultDesktopEditor%5B0%5D.ImageFormats%5B2%5D=bmp&DefaultDesktopEditor%5B0%5D.ImageFormats%5B3%5D=tiff&" + + "DefaultDesktopEditor%5B0%5D.ApplicationPath=test"; + expect(spyFetchPost).toHaveBeenCalledWith(new UrlQuery().UrlApiAppSettings(), query); + }); + + it("Create new item in Array if emthy array", async () => { + const value = { + target: { innerText: "test" } + } as unknown as ChangeEvent; + + const mockIConnectionDefault: Promise = Promise.resolve({ + data: null, + statusCode: 200 + }); + + const spyFetchPost = jest + .spyOn(FetchPost, "default") + .mockReset() + .mockImplementationOnce(() => mockIConnectionDefault); + + const setIsMessage = jest.fn(); + await UpdateDefaultEditorPhotos(value, setIsMessage, "error_here", "success", [ + { + applicationPath: "/exist_app", + imageFormats: [ImageFormat.gif] + } + ]); + expect(setIsMessage).toHaveBeenCalled(); + expect(setIsMessage).toHaveBeenCalledWith("success"); + + expect(spyFetchPost).toHaveBeenCalled(); + + const query2 = + "DefaultDesktopEditor%5B0%5D.ImageFormats%5B0%5D=jpg&DefaultDesktopEditor%5B0%5D.ImageFormats%5B1%5D=png&DefaultDesktopEditor" + + "%5B0%5D.ImageFormats%5B2%5D=bmp&DefaultDesktopEditor%5B0%5D.ImageFormats%5B3%5D=tiff&DefaultDesktopEditor%5B0%5D.ApplicationPath=%2Fexist_app&" + + "DefaultDesktopEditor%5B1%5D.ImageFormats%5B0%5D=jpg&DefaultDesktopEditor%5B1%5D.ImageFormats%5B1%5D=png&DefaultDesktopEditor%5B1%5D.ImageFormats%5B2%5D=bmp&" + + "DefaultDesktopEditor%5B1%5D.ImageFormats%5B3%5D=tiff&DefaultDesktopEditor%5B1%5D.ApplicationPath=test"; + + expect(spyFetchPost).toHaveBeenCalledWith(new UrlQuery().UrlApiAppSettings(), query2); + }); }); describe("ToggleCollections", () => { - it("should call FetchPost with correct URL and bodyParams for toggleCollections function", async () => { + it("should call FetchPost with correct URL and bodyParams for toggleCollections function Jpeg", async () => { const mockIConnectionDefault: Promise = Promise.resolve({ data: null, statusCode: 200 @@ -262,9 +406,10 @@ describe("ToggleCollections", () => { const spyFetchPost = jest .spyOn(FetchPost, "default") + .mockReset() .mockImplementationOnce(() => mockIConnectionDefault); const appSettings = { - desktopCollectionsOpen: RawJpegMode.Default, + desktopCollectionsOpen: RawJpegMode.Jpeg, useLocalDesktop: true } as unknown as IAppSettings; @@ -272,7 +417,54 @@ describe("ToggleCollections", () => { expect(spyFetchPost).toHaveBeenCalledWith( expect.stringContaining(new UrlQuery().UrlApiAppSettings()), - expect.any(String) + "desktopCollectionsOpen=2" + ); + }); + + it("should call FetchPost with correct URL and bodyParams for toggleCollections function Raw", async () => { + const mockIConnectionDefault: Promise = Promise.resolve({ + data: null, + statusCode: 200 + }); + + const spyFetchPost = jest + .spyOn(FetchPost, "default") + .mockReset() + .mockImplementationOnce(() => mockIConnectionDefault); + const appSettings = { + desktopCollectionsOpen: RawJpegMode.Raw, + useLocalDesktop: true + } as unknown as IAppSettings; + + await ToggleCollections(false, jest.fn(), "", "", appSettings); + + expect(spyFetchPost).toHaveBeenCalledWith( + expect.stringContaining(new UrlQuery().UrlApiAppSettings()), + "desktopCollectionsOpen=1" ); }); + + it("ToggleCollections call error if fetchPost is Error 500", async () => { + const appSettings = { + desktopCollectionsOpen: RawJpegMode.Default, + useLocalDesktop: true + } as unknown as IAppSettings; + + const mockIConnectionDefault: Promise = Promise.resolve({ + data: null, + statusCode: 500 + }); + + const spyFetchPost = jest + .spyOn(FetchPost, "default") + .mockImplementationOnce(() => mockIConnectionDefault); + + const setIsMessage = jest.fn(); + await ToggleCollections(true, setIsMessage, "error_here", "", appSettings); + + expect(setIsMessage).toHaveBeenCalled(); + expect(setIsMessage).toHaveBeenCalledWith("error_here"); + + expect(spyFetchPost).toHaveBeenCalled(); + }); }); From b1011ae7dcac5e2ba49eb88c705449befb279ed5 Mon Sep 17 00:00:00 2001 From: Dion Date: Tue, 27 Feb 2024 22:44:48 +0100 Subject: [PATCH 117/125] add tests --- .../Services/UserManagerTest.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/starsky/starskytest/starsky.foundation.accountmanagement/Services/UserManagerTest.cs b/starsky/starskytest/starsky.foundation.accountmanagement/Services/UserManagerTest.cs index 005d003b53..47f91797db 100644 --- a/starsky/starskytest/starsky.foundation.accountmanagement/Services/UserManagerTest.cs +++ b/starsky/starskytest/starsky.foundation.accountmanagement/Services/UserManagerTest.cs @@ -564,6 +564,44 @@ public void GetRole_NotExists() var result = userManager.GetRole("test12", "test"); Assert.IsNull(result); } + + [TestMethod] + public async Task GetRoleAsync_NotExists() + { + var userManager = new UserManager(_dbContext,new AppSettings(), new FakeIWebLogger(), _memoryCache); + var result = await userManager.GetRoleAsync(453454); + Assert.IsNull(result); + } + + [TestMethod] + public async Task GetRoleAsync_Exists() + { + _dbContext.Users.Add(new User{ Id = 45475, Name = "test"}); + _dbContext.Roles.Add(new Role { Code = "test_role_892453", Name = "test", Id = 47583945}); + _dbContext.UserRoles.Add(new UserRole{ UserId = 45475, RoleId = 47583945}); + + await _dbContext.SaveChangesAsync(); + var role = + await _dbContext.Roles.FirstOrDefaultAsync(p => p.Code == "test_role_892453"); + var userRole = + await _dbContext.UserRoles.FirstOrDefaultAsync(p => p.UserId == 45475); + var user= + await _dbContext.Users.FirstOrDefaultAsync(p => p.Id == 45475); + Assert.IsNotNull(role); + Assert.IsNotNull(userRole); + Assert.IsNotNull(user); + + var userManager = new UserManager(_dbContext,new AppSettings(), new FakeIWebLogger(), _memoryCache); + var result = await userManager.GetRoleAsync(45475); + + Assert.AreEqual("test_role_892453",result?.Code); + + _dbContext.Remove(role); + _dbContext.Remove(userRole); + _dbContext.Remove(user); + + await _dbContext.SaveChangesAsync(); + } [TestMethod] public async Task RemoveFromRole() From 322fc06097bf13d024a52600ed470e38c05f97a7 Mon Sep 17 00:00:00 2001 From: Dion Date: Tue, 27 Feb 2024 22:50:23 +0100 Subject: [PATCH 118/125] add test --- .../Helpers/Compare/AreListsEqualHelperTests.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/starsky/starskytest/starsky.foundation.platform/Helpers/Compare/AreListsEqualHelperTests.cs b/starsky/starskytest/starsky.foundation.platform/Helpers/Compare/AreListsEqualHelperTests.cs index 485c1b8ee8..ce2ba985cb 100644 --- a/starsky/starskytest/starsky.foundation.platform/Helpers/Compare/AreListsEqualHelperTests.cs +++ b/starsky/starskytest/starsky.foundation.platform/Helpers/Compare/AreListsEqualHelperTests.cs @@ -79,4 +79,18 @@ public void AreListsEqual_OneListNull_ArgumentNullException() // Assert Assert.IsFalse(result); } + + [TestMethod] + public void AreListsEqual_OneListNull() + { + // Arrange + var list1 = new List { 1, null, 3 }; + var list2 = new List { 1, 2, 3 }; + + // Act + var result = AreListsEqualHelper.AreListsEqual(list1, list2); + + // Assert // not sure if good + Assert.IsTrue(result); + } } From 0ffd351301a4d330c22f3543537a2e8afaaec278 Mon Sep 17 00:00:00 2001 From: Dion Date: Tue, 27 Feb 2024 23:02:35 +0100 Subject: [PATCH 119/125] add tests --- .../Helpers/WindowsOpenDesktopAppTests.cs | 33 ++++++++++- .../Helpers/EnumHelperTest.cs | 57 +++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs index 73c96b36da..34d30a987b 100644 --- a/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs +++ b/starsky/starskytest/starsky.foundation.native/OpenApplicationNative/Helpers/WindowsOpenDesktopAppTests.cs @@ -72,7 +72,7 @@ public void W_OpenDefault_NonWindows() var result = WindowsOpenDesktopApp.OpenDefault(["any value"], OSPlatform.Linux); Assert.IsNull(result); } - + [TestMethod] public void W_OpenDefault2_NonWindows() { @@ -82,6 +82,20 @@ public void W_OpenDefault2_NonWindows() return; } + var result = WindowsOpenDesktopApp.OpenDefault(["any value"], OSPlatform.Windows); + + Assert.IsTrue(result); + } + + [TestMethod] + public void W_OpenDefault3_NonWindows() + { + if ( new AppSettings().IsWindows ) + { + Assert.Inconclusive("This test if for Unix Only"); + return; + } + var result = WindowsOpenDesktopApp.OpenDefault(["any value"]); Console.WriteLine(result); @@ -121,7 +135,7 @@ public void W_OpenApplicationAtUrl_NonWindows() "app", OSPlatform.Linux); Assert.IsNull(result); } - + [TestMethod] [ExpectedException(typeof(Win32Exception))] public void W_OpenApplicationAtUrl2_NonWindows() @@ -132,6 +146,21 @@ public void W_OpenApplicationAtUrl2_NonWindows() return; } + // ExpectedException = Win32Exception + WindowsOpenDesktopApp.OpenApplicationAtUrl(["any value"], + "/not_found_849539453", OSPlatform.Windows); + } + + [TestMethod] + [ExpectedException(typeof(Win32Exception))] + public void W_OpenApplicationAtUrl3_NonWindows() + { + if ( new AppSettings().IsWindows ) + { + Assert.Inconclusive("This test if for Unix Only"); + return; + } + WindowsOpenDesktopApp.OpenApplicationAtUrl(new List { "any value" }, "app"); } diff --git a/starsky/starskytest/starsky.foundation.writemeta/Helpers/EnumHelperTest.cs b/starsky/starskytest/starsky.foundation.writemeta/Helpers/EnumHelperTest.cs index f5af9fb725..b036f03a3a 100644 --- a/starsky/starskytest/starsky.foundation.writemeta/Helpers/EnumHelperTest.cs +++ b/starsky/starskytest/starsky.foundation.writemeta/Helpers/EnumHelperTest.cs @@ -108,4 +108,61 @@ public void Test_GetDisplayName_ReturnsEmptyString_ForNullableEnumWithNullDispla // Assert Assert.AreEqual(null, result); } + + public enum TestType + { + [Display(Name = "First Display Name")] FirstValue, + + [Display(Name = "Second Display Name")] + SecondValue + } + + [TestMethod] + public void GetDisplayName_WithValidEnumValue_ReturnsCorrectDisplayName() + { + // Arrange + var enumValue = TestType.FirstValue; + + // Act + var displayName = EnumHelper.GetDisplayName(enumValue); + + // Assert + Assert.AreEqual("First Display Name", displayName); + } + + [TestMethod] + public void GetDisplayName_WithNullEnumValue_ReturnsNull() + { + // Arrange & Act + var displayName = EnumHelper.GetDisplayName(null!); + + // Assert + Assert.IsNull(displayName); + } + + [TestMethod] + public void GetDisplayName_WithInvalidEnumValue_ReturnsNull() + { + // Arrange + var enumValue = ( TestType )100; // An invalid value + + // Act + var displayName = EnumHelper.GetDisplayName(enumValue); + + // Assert + Assert.IsNull(displayName); + } + + [TestMethod] + public void GetDisplayName_WithEnumValueWithoutDisplayAttribute_ReturnsNull() + { + // Arrange + var enumValue = TestType.SecondValue; + + // Act + var displayName = EnumHelper.GetDisplayName(enumValue); + + // Assert + Assert.AreEqual("Second Display Name", displayName); + } } From 666ba6a1438da1fc72b36daac76875d57bc0ea06 Mon Sep 17 00:00:00 2001 From: Dion Date: Tue, 27 Feb 2024 23:04:00 +0100 Subject: [PATCH 120/125] rename --- .../preference-app-settings-desktop.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.spec.tsx b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.spec.tsx index ab2cc29049..b7daae8fa4 100644 --- a/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.spec.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/preference-app-settings-desktop/preference-app-settings-desktop.spec.tsx @@ -87,7 +87,7 @@ describe("PreferencesAppSettingsDesktop", () => { component.unmount(); }); - it("should render MessageSwitchButtonDesktopApplicationDescription when appSettings.useLocalDesktop is true", () => { + it("get application path from useFetch and display", () => { const mockGetIConnectionDefaultAppSettings = { statusCode: 200, data: { From 496961006c55258635707caf4d40106ae6596584 Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 28 Feb 2024 16:32:47 +0100 Subject: [PATCH 121/125] add german / move to localization.json --- .../docs/developer-guide/api/readme.md | 6 +- .../atoms/form-control/form-control.tsx | 8 +- .../menu-option-modal.spec.tsx | 9 +- .../menu-option-modal/menu-option-modal.tsx | 3 +- .../atoms/menu-option/menu-option.spec.tsx | 13 +- .../atoms/menu-option/menu-option.tsx | 3 +- .../components/atoms/more-menu/more-menu.tsx | 3 +- .../archive-pagination/archive-pagination.tsx | 5 +- ...chive-sidebar-label-edit-add-overwrite.tsx | 31 +- ...hive-sidebar-label-edit-search-replace.tsx | 31 +- .../archive-sidebar-label-edit.tsx | 7 +- .../archive-sidebar-selection-list.tsx | 5 +- .../color-class-select-keyboard.tsx | 6 +- .../color-class-select/color-class-select.tsx | 19 +- .../color-class-update-single.ts | 12 +- .../force-sync-wait-button.tsx | 8 +- .../health-check-for-updates.tsx | 20 +- .../health-status-error.tsx | 10 +- .../item-text-list-view.tsx | 3 +- .../modal-drop-area-files-added.tsx | 6 +- .../search-pagination/search-pagination.tsx | 5 +- .../application-exception.tsx | 11 +- .../archive-sidebar/archive-sidebar.tsx | 10 +- .../detail-view-sidebar.tsx | 29 +- .../detailview-info-datetime.tsx | 10 +- .../detailview-info-location.tsx | 8 +- .../organisms/menu-search/menu-search.tsx | 2 +- .../organisms/menu-trash/menu-trash.tsx | 8 +- .../modal-archive-mkdir.tsx | 22 +- .../modal-archive-rename.tsx | 13 +- .../modal-archive-synchronize-manually.tsx | 34 +- .../modal-detailview-rename-file.tsx | 20 +- .../modal-display-options.tsx | 39 +- .../modal-download/modal-download.tsx | 27 +- .../modal-edit-datetime.tsx | 16 +- .../modal-force-delete/modal-force-delete.tsx | 11 +- .../organisms/modal-publish/modal-publish.tsx | 41 +- .../src/containers/account-register.tsx | 38 +- .../clientapp/src/containers/login.tsx | 50 +- .../src/containers/media-content.tsx | 9 +- .../containers/preferences/preferences.tsx | 3 +- .../clientapp/src/containers/search.tsx | 12 +- .../clientapp/src/containers/trash.tsx | 11 +- .../src/interfaces/ILanguageLocalization.ts | 11 + .../src/localization/localization.json | 521 +++++++++++++++++- .../clientapp/src/pages/not-found-page.tsx | 5 +- .../clientapp/src/shared/language.spec.ts | 42 +- .../starsky/clientapp/src/shared/language.ts | 22 +- starsky/starsky/clientapp/vite.config.ts | 2 +- 49 files changed, 836 insertions(+), 404 deletions(-) create mode 100644 starsky/starsky/clientapp/src/interfaces/ILanguageLocalization.ts diff --git a/documentation/docs/developer-guide/api/readme.md b/documentation/docs/developer-guide/api/readme.md index 8192afde44..7825f2d3f9 100644 --- a/documentation/docs/developer-guide/api/readme.md +++ b/documentation/docs/developer-guide/api/readme.md @@ -30,6 +30,9 @@ This document is auto generated | __/api/delete__ | DELETE| Remove files from the disk, but the file must contain the !delete! (TrashKeyw...| | _Parameters: f (subPaths, separated by dot comma), collections (true is to update files with the same name before _ | | _ the extenstion) _ | +| __/api/desktop-editor/open__ | GET | Open a file in the default editor or a specific editor on the desktop | +| _Parameters: f (single or multiple subPaths), collections (to combine files with the same name before the extension) _ | +| __/api/desktop-editor/amount-confirmation__ | GET | Check the amount of files to open before | | __/api/disk/mkdir__ | POST | Make a directory (-p) | | __/api/disk/rename__ | POST | Rename file/folder and update it in the database | | _Parameters: f (from subPath), to (to subPath), collections (is collections bool), currentStatus (default is to not _ | @@ -103,7 +106,6 @@ This document is auto generated | _ json (text as output), extraLarge (give preference to extraLarge over large image) _ | | __/api/thumbnail/zoom/\{f\}@\{z\}__ | GET | Get zoomed in image by fileHash.At the moment this is the source image | | __/api/thumbnail-generation__ | POST | Create thumbnails for a folder in the background | -| __/api/trash/detect-to-use-system-trash__ | GET | Is the system trash supported | -| __/api/trash/move-to-trash__ | POST | (beta) Move a file to the trash | +| __/api/trash/move-to-trash__ | POST | Move a file to the trash | | __/api/upload__ | POST | Upload to specific folder (does not check if already has been imported)Use th...| | __/api/upload-sidecar__ | POST | Upload sidecar file to specific folder (does not check if already has been im...| diff --git a/starsky/starsky/clientapp/src/components/atoms/form-control/form-control.tsx b/starsky/starsky/clientapp/src/components/atoms/form-control/form-control.tsx index 7c9722f343..247facd03d 100644 --- a/starsky/starsky/clientapp/src/components/atoms/form-control/form-control.tsx +++ b/starsky/starsky/clientapp/src/components/atoms/form-control/form-control.tsx @@ -1,5 +1,6 @@ import React, { useState } from "react"; import useGlobalSettings from "../../../hooks/use-global-settings"; +import localization from "../../../localization/localization.json"; import { Language } from "../../../shared/language"; import { LimitLength } from "./limit-length"; @@ -27,11 +28,8 @@ const FormControl: React.FunctionComponent = ({ onBlur, ...pr // content const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageFieldMaxLength = language.token( - language.text( - "Het onderstaande veld mag maximaal {maxlength} tekens hebben", - "The field below can have a maximum of {maxlength} characters" - ), + const MessageFieldMaxLength = language.key( + localization.MessageFieldMaxLength, ["{maxlength}"], [maxlength.toString()] ); diff --git a/starsky/starsky/clientapp/src/components/atoms/menu-option-modal/menu-option-modal.spec.tsx b/starsky/starsky/clientapp/src/components/atoms/menu-option-modal/menu-option-modal.spec.tsx index b6609b511d..a6aebc150c 100644 --- a/starsky/starsky/clientapp/src/components/atoms/menu-option-modal/menu-option-modal.spec.tsx +++ b/starsky/starsky/clientapp/src/components/atoms/menu-option-modal/menu-option-modal.spec.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from "@testing-library/react"; +import { LanguageLocalizationExample } from "../../../interfaces/ILanguageLocalization.ts"; import MenuOptionModal from "./menu-option-modal.tsx"; describe("MenuOption component", () => { @@ -7,7 +8,7 @@ describe("MenuOption component", () => { const setEnableMoreMenuMock = jest.fn(); render( { ); expect(screen.getByTestId("test")).toBeTruthy(); - expect(screen.getByTestId("test").innerHTML).toBe("Content"); + expect(screen.getByTestId("test").innerHTML).toBe(LanguageLocalizationExample.en); }); it("expect child no localisation field", () => { @@ -44,7 +45,7 @@ describe("MenuOption component", () => { const setEnableMoreMenuMock = jest.fn(); render( { const setEnableMoreMenuMock = jest.fn(); render( >; - localization?: { nl: string; en: string }; + localization?: ILanguageLocalization; setEnableMoreMenu?: React.Dispatch>; children?: React.ReactNode; } diff --git a/starsky/starsky/clientapp/src/components/atoms/menu-option/menu-option.spec.tsx b/starsky/starsky/clientapp/src/components/atoms/menu-option/menu-option.spec.tsx index b4a5872ac6..e41bffc886 100644 --- a/starsky/starsky/clientapp/src/components/atoms/menu-option/menu-option.spec.tsx +++ b/starsky/starsky/clientapp/src/components/atoms/menu-option/menu-option.spec.tsx @@ -1,11 +1,12 @@ import { fireEvent, render, screen } from "@testing-library/react"; +import { LanguageLocalizationExample } from "../../../interfaces/ILanguageLocalization"; import MenuOption from "./menu-option"; describe("MenuOption component", () => { it("expect content", () => { render( {}} testName="test" isReadOnly={false} @@ -13,7 +14,7 @@ describe("MenuOption component", () => { ); expect(screen.getByTestId("test")).toBeTruthy(); - expect(screen.getByTestId("test").innerHTML).toBe("Content"); + expect(screen.getByTestId("test").innerHTML).toBe(LanguageLocalizationExample.en); }); it("expect child no localisation field", () => { @@ -30,7 +31,7 @@ describe("MenuOption component", () => { it("renders correctly with default props", () => { render( {}} testName="test-menu-option" isReadOnly={false} @@ -44,7 +45,7 @@ describe("MenuOption component", () => { it("renders correctly with custom props", () => { render( {}} testName="test-menu-option1" isReadOnly={false} @@ -59,7 +60,7 @@ describe("MenuOption component", () => { render( @@ -74,7 +75,7 @@ describe("MenuOption component", () => { render( diff --git a/starsky/starsky/clientapp/src/components/atoms/menu-option/menu-option.tsx b/starsky/starsky/clientapp/src/components/atoms/menu-option/menu-option.tsx index d7621712c2..bd180bd4b7 100644 --- a/starsky/starsky/clientapp/src/components/atoms/menu-option/menu-option.tsx +++ b/starsky/starsky/clientapp/src/components/atoms/menu-option/menu-option.tsx @@ -1,12 +1,13 @@ import React, { memo } from "react"; import useGlobalSettings from "../../../hooks/use-global-settings"; +import { ILanguageLocalization } from "../../../interfaces/ILanguageLocalization"; import { Language } from "../../../shared/language"; interface IMenuOptionProps { isReadOnly: boolean; testName: string; onClickKeydown: () => void; - localization?: { nl: string; en: string }; + localization?: ILanguageLocalization; children?: React.ReactNode; } diff --git a/starsky/starsky/clientapp/src/components/atoms/more-menu/more-menu.tsx b/starsky/starsky/clientapp/src/components/atoms/more-menu/more-menu.tsx index 65b8b8ccc1..bd115587d0 100644 --- a/starsky/starsky/clientapp/src/components/atoms/more-menu/more-menu.tsx +++ b/starsky/starsky/clientapp/src/components/atoms/more-menu/more-menu.tsx @@ -1,5 +1,6 @@ import React, { useEffect } from "react"; import useGlobalSettings from "../../../hooks/use-global-settings"; +import localization from "../../../localization/localization.json"; import { Language } from "../../../shared/language"; type MoreMenuPropTypes = { @@ -17,7 +18,7 @@ const MoreMenu: React.FunctionComponent = ({ }) => { const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageMore = language.text("Meer", "More"); + const MessageMore = language.key(localization.MessageMore); const offMoreMenu = () => setEnableMoreMenu(false); diff --git a/starsky/starsky/clientapp/src/components/molecules/archive-pagination/archive-pagination.tsx b/starsky/starsky/clientapp/src/components/molecules/archive-pagination/archive-pagination.tsx index e310646146..bb4aec9542 100644 --- a/starsky/starsky/clientapp/src/components/molecules/archive-pagination/archive-pagination.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/archive-pagination/archive-pagination.tsx @@ -2,6 +2,7 @@ import React, { memo } from "react"; import useGlobalSettings from "../../../hooks/use-global-settings"; import useLocation from "../../../hooks/use-location/use-location"; import { IRelativeObjects } from "../../../interfaces/IDetailView"; +import localization from "../../../localization/localization.json"; import { Language } from "../../../shared/language"; import { UrlQuery } from "../../../shared/url-query"; import Link from "../../atoms/link/link"; @@ -17,8 +18,8 @@ const ArchivePagination: React.FunctionComponent = memo((props) = // content const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessagePrevious = language.text("Vorige", "Previous"); - const MessageNext = language.text("Volgende", "Next"); + const MessagePrevious = language.key(localization.MessagePrevious); + const MessageNext = language.key(localization.MessageNext); // used for reading current location const history = useLocation(); diff --git a/starsky/starsky/clientapp/src/components/molecules/archive-sidebar/archive-sidebar-label-edit-add-overwrite.tsx b/starsky/starsky/clientapp/src/components/molecules/archive-sidebar/archive-sidebar-label-edit-add-overwrite.tsx index 2251879c1f..59bb977abe 100644 --- a/starsky/starsky/clientapp/src/components/molecules/archive-sidebar/archive-sidebar-label-edit-add-overwrite.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/archive-sidebar/archive-sidebar-label-edit-add-overwrite.tsx @@ -6,6 +6,7 @@ import useLocation from "../../../hooks/use-location/use-location"; import { PageType } from "../../../interfaces/IDetailView"; import { IExifStatus } from "../../../interfaces/IExifStatus"; import { ISidebarUpdate } from "../../../interfaces/ISidebarUpdate"; +import localization from "../../../localization/localization.json"; import { CastToInterface } from "../../../shared/cast-to-interface"; import FetchPost from "../../../shared/fetch/fetch-post"; import { Keyboard } from "../../../shared/keyboard"; @@ -20,24 +21,14 @@ import Preloader from "../../atoms/preloader/preloader"; const ArchiveSidebarLabelEditAddOverwrite: React.FunctionComponent = () => { const settings = useGlobalSettings(); - const MessageAddName = new Language(settings.language).text("Toevoegen", "Add to"); - const MessageOverwriteName = new Language(settings.language).text("Overschrijven", "Overwrite"); - const MessageTitleName = new Language(settings.language).text("Titel", "Title"); - const MessageErrorReadOnly = new Language(settings.language).text( - "Eén of meerdere bestanden zijn alleen lezen. " + - "Alleen de bestanden met schrijfrechten zijn geupdate.", - "One or more files are read only. " + "Only the files with write permissions have been updated." - ); - const MessageErrorGenericFail = new Language(settings.language).text( - "Er is iets misgegaan met het updaten. Probeer het opnieuw", - "Something went wrong with the update. Please try again" - ); - - const MessageErrorNotFoundSourceMissing = new Language(settings.language).text( - "Eén of meerdere bestanden zijn al verdwenen. " + - "Alleen de bestanden die wel aanwezig zijn geupdate. Draai een handmatige sync", - "One or more files are already gone. " + - "Only the files that are present are updated. Run a manual sync" + const language = new Language(settings.language); + const MessageAddName = language.key(localization.MessageAddName); + const MessageOverwriteName = language.key(localization.MessageOverwriteName); + const MessageTitleName = language.key(localization.MessageTitleName); + const MessageWriteErrorReadOnly = language.key(localization.MessageWriteErrorReadOnly); + const MessageErrorGenericFail = language.key(localization.MessageErrorGenericFail); + const MessageErrorNotFoundSourceMissingRunSync = language.key( + localization.MessageErrorNotFoundSourceMissingRunSync ); const history = useLocation(); @@ -107,9 +98,9 @@ const ArchiveSidebarLabelEditAddOverwrite: React.FunctionComponent = () => { .then((anyData) => { const result = new CastToInterface().InfoFileIndexArray(anyData.data); result.forEach((element) => { - if (element.status === IExifStatus.ReadOnly) setIsError(MessageErrorReadOnly); + if (element.status === IExifStatus.ReadOnly) setIsError(MessageWriteErrorReadOnly); if (element.status === IExifStatus.NotFoundSourceMissing) - setIsError(MessageErrorNotFoundSourceMissing); + setIsError(MessageErrorNotFoundSourceMissingRunSync); if (element.status === IExifStatus.Ok || element.status === IExifStatus.Deleted) { dispatch({ type: "update", diff --git a/starsky/starsky/clientapp/src/components/molecules/archive-sidebar/archive-sidebar-label-edit-search-replace.tsx b/starsky/starsky/clientapp/src/components/molecules/archive-sidebar/archive-sidebar-label-edit-search-replace.tsx index 161ce16fb9..18708c71c7 100644 --- a/starsky/starsky/clientapp/src/components/molecules/archive-sidebar/archive-sidebar-label-edit-search-replace.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/archive-sidebar/archive-sidebar-label-edit-search-replace.tsx @@ -5,6 +5,7 @@ import useLocation from "../../../hooks/use-location/use-location"; import { PageType } from "../../../interfaces/IDetailView"; import { IExifStatus } from "../../../interfaces/IExifStatus"; import { ISidebarUpdate } from "../../../interfaces/ISidebarUpdate"; +import localization from "../../../localization/localization.json"; import { CastToInterface } from "../../../shared/cast-to-interface"; import FetchPost from "../../../shared/fetch/fetch-post"; import { Language } from "../../../shared/language"; @@ -19,22 +20,14 @@ import Preloader from "../../atoms/preloader/preloader"; const ArchiveSidebarLabelEditSearchReplace: React.FunctionComponent = () => { const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageSearchAndReplaceName = language.text("Zoeken en vervangen", "Search and replace"); - const MessageTitleName = language.text("Titel", "Title"); - const MessageErrorReadOnly = new Language(settings.language).text( - "Eén of meerdere bestanden zijn alleen lezen. " + - "Alleen de bestanden met schrijfrechten zijn geupdate.", - "One or more files are read only. " + "Only the files with write permissions have been updated." + const MessageSearchAndReplaceNameLong = language.key( + localization.MessageSearchAndReplaceNameLong ); - const MessageErrorNotFoundSourceMissing = new Language(settings.language).text( - "Eén of meerdere bestanden zijn al verdwenen. " + - "Alleen de bestanden die wel aanwezig zijn geupdate. Draai een handmatige sync", - "One or more files are already gone. " + - "Only the files that are present are updated. Run a manual sync" - ); - const MessageErrorGenericFail = new Language(settings.language).text( - "Er is iets misgegaan met het updaten. Probeer het opnieuw", - "Something went wrong with the update. Please try again" + const MessageTitleName = language.key(localization.MessageTitleName); + const MessageWriteErrorReadOnly = language.key(localization.MessageWriteErrorReadOnly); + const MessageErrorGenericFail = language.key(localization.MessageErrorGenericFail); + const MessageErrorNotFoundSourceMissingRunSync = language.key( + localization.MessageErrorNotFoundSourceMissingRunSync ); const history = useLocation(); @@ -94,9 +87,9 @@ const ArchiveSidebarLabelEditSearchReplace: React.FunctionComponent = () => { function handleFetchPostResponse(anyData: any) { const result = new CastToInterface().InfoFileIndexArray(anyData.data); result.forEach((element) => { - if (element.status === IExifStatus.ReadOnly) setIsError(MessageErrorReadOnly); + if (element.status === IExifStatus.ReadOnly) setIsError(MessageWriteErrorReadOnly); if (element.status === IExifStatus.NotFoundSourceMissing) - setIsError(MessageErrorNotFoundSourceMissing); + setIsError(MessageErrorNotFoundSourceMissingRunSync); if (element.status === IExifStatus.Ok || element.status === IExifStatus.Deleted) { dispatch({ type: "update", @@ -238,11 +231,11 @@ const ArchiveSidebarLabelEditSearchReplace: React.FunctionComponent = () => { data-test="replace-button" onClick={() => pushSearchAndReplace()} > - {MessageSearchAndReplaceName} + {MessageSearchAndReplaceNameLong} ) : ( )} diff --git a/starsky/starsky/clientapp/src/components/molecules/archive-sidebar/archive-sidebar-label-edit.tsx b/starsky/starsky/clientapp/src/components/molecules/archive-sidebar/archive-sidebar-label-edit.tsx index c24f5032f3..177532dea4 100644 --- a/starsky/starsky/clientapp/src/components/molecules/archive-sidebar/archive-sidebar-label-edit.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/archive-sidebar/archive-sidebar-label-edit.tsx @@ -1,6 +1,7 @@ import { useContext, useState } from "react"; import { ArchiveContext } from "../../../contexts/archive-context"; import useGlobalSettings from "../../../hooks/use-global-settings"; +import localization from "../../../localization/localization.json"; import { CastToInterface } from "../../../shared/cast-to-interface"; import { Language } from "../../../shared/language"; import SwitchButton from "../../atoms/switch-button/switch-button"; @@ -10,8 +11,10 @@ import ArchiveSidebarLabelEditSearchReplace from "./archive-sidebar-label-edit-s const ArchiveSidebarLabelEdit: React.FunctionComponent = () => { // Content const settings = useGlobalSettings(); - const MessageModifyName = new Language(settings.language).text("Wijzigen", "Modify"); - const MessageSearchAndReplaceName = new Language(settings.language).text("Vervangen", "Replace"); + const MessageModifyName = new Language(settings.language).key(localization.MessageModifyName); + const MessageSearchAndReplaceName = new Language(settings.language).key( + localization.MessageSearchAndReplaceNameShort + ); // Toggle const [replaceMode, setReplaceMode] = useState(false); diff --git a/starsky/starsky/clientapp/src/components/molecules/archive-sidebar/archive-sidebar-selection-list.tsx b/starsky/starsky/clientapp/src/components/molecules/archive-sidebar/archive-sidebar-selection-list.tsx index 5ba0e6219a..297af8a6d7 100644 --- a/starsky/starsky/clientapp/src/components/molecules/archive-sidebar/archive-sidebar-selection-list.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/archive-sidebar/archive-sidebar-selection-list.tsx @@ -3,6 +3,7 @@ import useGlobalSettings from "../../../hooks/use-global-settings"; import useLocation from "../../../hooks/use-location/use-location"; import { IArchiveProps } from "../../../interfaces/IArchiveProps"; import { IFileIndexItem } from "../../../interfaces/IFileIndexItem"; +import localization from "../../../localization/localization.json"; import { Language } from "../../../shared/language"; import { Select } from "../../../shared/select"; import { URLPath } from "../../../shared/url-path"; @@ -16,8 +17,8 @@ const ArchiveSidebarSelectionList: React.FunctionComponent setIsDone(""); }, [props.filePath]); - const MessageColorClassIsUpdated = new Language(settings.language).text( - "Colorclass is bijgewerkt", - "Colorclass is updated" + const MessageColorClassIsUpdated = new Language(settings.language).key( + localization.MessageColorClassIsUpdated ); useKeyboardEvent(/[0-8]/, (event: KeyboardEvent) => { diff --git a/starsky/starsky/clientapp/src/components/molecules/color-class-select/color-class-select.tsx b/starsky/starsky/clientapp/src/components/molecules/color-class-select/color-class-select.tsx index 3f1cb65539..a35d65da5a 100644 --- a/starsky/starsky/clientapp/src/components/molecules/color-class-select/color-class-select.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/color-class-select/color-class-select.tsx @@ -1,6 +1,7 @@ import "core-js/modules/es.array.find"; import React, { useEffect, useState } from "react"; import useGlobalSettings from "../../../hooks/use-global-settings"; +import localization from "../../../localization/localization.json"; import { Language } from "../../../shared/language"; import Notification, { NotificationType } from "../../atoms/notification/notification"; import Portal from "../../atoms/portal/portal"; @@ -25,15 +26,15 @@ const ColorClassSelect: React.FunctionComponent = (props const language = new Language(settings.language); const colorContent: Array = [ - language.text("Kleurloos", "Colorless"), - language.text("Roze", "Pink"), - language.text("Rood", "Red"), - language.text("Oranje", "Orange"), - language.text("Geel", "Yellow"), - language.text("Groen", "Green"), - language.text("Azuur", "Azure"), - language.text("Blauw", "Blue"), - language.text("Grijs", "Grey") + language.key(localization.ColorClassColour0), + language.key(localization.ColorClassColour1), + language.key(localization.ColorClassColour2), + language.key(localization.ColorClassColour3), + language.key(localization.ColorClassColour4), + language.key(localization.ColorClassColour5), + language.key(localization.ColorClassColour6), + language.key(localization.ColorClassColour7), + language.key(localization.ColorClassColour8) ]; const [currentColorClass, setCurrentColorClass] = React.useState(props.currentColorClass); diff --git a/starsky/starsky/clientapp/src/components/molecules/color-class-select/color-class-update-single.ts b/starsky/starsky/clientapp/src/components/molecules/color-class-select/color-class-update-single.ts index 0881cc4b3b..deea83a8bb 100644 --- a/starsky/starsky/clientapp/src/components/molecules/color-class-select/color-class-update-single.ts +++ b/starsky/starsky/clientapp/src/components/molecules/color-class-select/color-class-update-single.ts @@ -1,5 +1,6 @@ import { IGlobalSettings } from "../../../hooks/use-global-settings"; import { IExifStatus } from "../../../interfaces/IExifStatus"; +import localization from "../../../localization/localization.json"; import { CastToInterface } from "../../../shared/cast-to-interface"; import FetchPost from "../../../shared/fetch/fetch-post"; import { Language, SupportedLanguages } from "../../../shared/language"; @@ -38,13 +39,8 @@ export class ColorClassUpdateSingle { this.clearAfter = clearAfter; } - private getMessageErrorReadOnly() { - return new Language(this.language).text( - "Eén of meerdere bestanden zijn alleen lezen. " + - "Alleen de bestanden met schrijfrechten zijn geupdate.", - "One or more files are read only. " + - "Only the files with write permissions have been updated." - ); + private getMessageWriteErrorReadOnly() { + return new Language(this.language).key(localization.MessageWriteErrorReadOnly); } public Update(colorClass: number) { @@ -67,7 +63,7 @@ export class ColorClassUpdateSingle { return item.status === IExifStatus.ReadOnly; }) ) { - this.setIsError(this.getMessageErrorReadOnly()); + this.setIsError(this.getMessageWriteErrorReadOnly()); return; } this.setCurrentColorClass(colorClass); diff --git a/starsky/starsky/clientapp/src/components/molecules/force-sync-wait-button/force-sync-wait-button.tsx b/starsky/starsky/clientapp/src/components/molecules/force-sync-wait-button/force-sync-wait-button.tsx index e03eba18f6..cb360e0a1b 100644 --- a/starsky/starsky/clientapp/src/components/molecules/force-sync-wait-button/force-sync-wait-button.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/force-sync-wait-button/force-sync-wait-button.tsx @@ -3,6 +3,7 @@ import { ArchiveAction } from "../../../contexts/archive-context"; import useGlobalSettings from "../../../hooks/use-global-settings"; import { IArchiveProps } from "../../../interfaces/IArchiveProps"; import { IConnectionDefault } from "../../../interfaces/IConnectionDefault"; +import localization from "../../../localization/localization.json"; import { CastToInterface } from "../../../shared/cast-to-interface"; import FetchGet from "../../../shared/fetch/fetch-get"; import FetchPost from "../../../shared/fetch/fetch-post"; @@ -64,10 +65,7 @@ const ForceSyncWaitButton: React.FunctionComponent const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageForceSync = language.text( - "Handmatig synchroniseren van huidige map", - "Synchronize current directory manually" - ); + const MessageForceSyncCurrentFolder = language.key(localization.MessageForceSyncCurrentFolder); const [startCounter, setStartCounter] = useState(0); // preloading icon @@ -101,7 +99,7 @@ const ForceSyncWaitButton: React.FunctionComponent <> {isLoading ? : ""} ); diff --git a/starsky/starsky/clientapp/src/components/molecules/health-check-for-updates/health-check-for-updates.tsx b/starsky/starsky/clientapp/src/components/molecules/health-check-for-updates/health-check-for-updates.tsx index ae333a17c6..d3f7218264 100644 --- a/starsky/starsky/clientapp/src/components/molecules/health-check-for-updates/health-check-for-updates.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/health-check-for-updates/health-check-for-updates.tsx @@ -1,5 +1,6 @@ import useFetch from "../../../hooks/use-fetch"; import useGlobalSettings from "../../../hooks/use-global-settings"; +import localization from "../../../localization/localization.json"; import { BrowserDetect } from "../../../shared/browser-detect"; import { DifferenceInDate } from "../../../shared/date"; import { Language } from "../../../shared/language"; @@ -35,24 +36,15 @@ const HealthCheckForUpdates: React.FunctionComponent = () => { const language = new Language(settings.language); - const ReleasesUrlToken = - " {releasesToken}"; - let WhereToFindRelease = language.token( - ReleasesUrlToken, + let WhereToFindRelease = language.key( + localization.MessageWhereToFindReleaseReleasesUrlTokenHtml, ["{releasesToken}"], - [language.text("Ga naar het release overzicht", "Go to the release overview")] + [language.key(localization.MessageWhereToFindReleaseReleasesUrlTokenContent)] ); if (new BrowserDetect().IsElectronApp()) - WhereToFindRelease = language.text( - "Ga naar het Help menu en dan release overzicht", - "Go to the release overview" - ); + WhereToFindRelease = language.key(localization.WhereToFindReleaseElectronApp); - const MessageNewVersionUpdateToken = language.text( - "Er is een nieuwe versie beschikbaar {WhereToFindRelease}", - "A new version is available {WhereToFindRelease}" - ); + const MessageNewVersionUpdateToken = language.key(localization.MessageNewVersionUpdateToken); const MessageNewVersionUpdateHtml = language.token( MessageNewVersionUpdateToken, diff --git a/starsky/starsky/clientapp/src/components/molecules/health-status-error/health-status-error.tsx b/starsky/starsky/clientapp/src/components/molecules/health-status-error/health-status-error.tsx index 7449cea9bf..bb2e197cda 100644 --- a/starsky/starsky/clientapp/src/components/molecules/health-status-error/health-status-error.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/health-status-error/health-status-error.tsx @@ -1,6 +1,7 @@ import useFetch from "../../../hooks/use-fetch"; import useGlobalSettings from "../../../hooks/use-global-settings"; import { IHealthEntry } from "../../../interfaces/IHealthEntry"; +import localization from "../../../localization/localization.json"; import { Language } from "../../../shared/language"; import { UrlQuery } from "../../../shared/url-query"; import Notification, { NotificationType } from "../../atoms/notification/notification"; @@ -9,9 +10,8 @@ const HealthStatusError: React.FunctionComponent = () => { const healthCheck = useFetch(new UrlQuery().UrlHealthDetails(), "get"); const settings = useGlobalSettings(); - const MessageCriticalErrors = new Language(settings.language).text( - "Er zijn kritieke fouten in de volgende onderdelen:", - "There are critical errors in the following components:" + const MessageHealthStatusCriticalErrors = new Language(settings.language).key( + localization.MessageHealthStatusCriticalErrorsWithTheFollowingComponents ); if ( @@ -21,7 +21,9 @@ const HealthStatusError: React.FunctionComponent = () => { ) return null; - const content: React.JSX.Element[] = [{MessageCriticalErrors}]; + const content: React.JSX.Element[] = [ + {MessageHealthStatusCriticalErrors} + ]; if (!healthCheck.data?.entries) { content.push( diff --git a/starsky/starsky/clientapp/src/components/molecules/item-text-list-view/item-text-list-view.tsx b/starsky/starsky/clientapp/src/components/molecules/item-text-list-view/item-text-list-view.tsx index 9f6d50b93c..442b209c1e 100644 --- a/starsky/starsky/clientapp/src/components/molecules/item-text-list-view/item-text-list-view.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/item-text-list-view/item-text-list-view.tsx @@ -1,6 +1,7 @@ import useGlobalSettings from "../../../hooks/use-global-settings"; import { IExifStatus } from "../../../interfaces/IExifStatus"; import { IFileIndexItem } from "../../../interfaces/IFileIndexItem"; +import localization from "../../../localization/localization.json"; import { Language } from "../../../shared/language"; interface ItemListProps { @@ -27,7 +28,7 @@ const ItemTextListView: React.FunctionComponent = (props) => { // Content const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageNoPhotos = language.text("Er zijn geen foto's", "There are no pictures"); + const MessageNoPhotos = language.key(localization.MessageNoPhotos); if (!props.fileIndexItems) return ( diff --git a/starsky/starsky/clientapp/src/components/molecules/modal-drop-area-files-added/modal-drop-area-files-added.tsx b/starsky/starsky/clientapp/src/components/molecules/modal-drop-area-files-added/modal-drop-area-files-added.tsx index 173edb3a7a..686122d828 100644 --- a/starsky/starsky/clientapp/src/components/molecules/modal-drop-area-files-added/modal-drop-area-files-added.tsx +++ b/starsky/starsky/clientapp/src/components/molecules/modal-drop-area-files-added/modal-drop-area-files-added.tsx @@ -1,5 +1,6 @@ import useGlobalSettings from "../../../hooks/use-global-settings"; import { IFileIndexItem } from "../../../interfaces/IFileIndexItem"; +import localization from "../../../localization/localization.json"; import { Language } from "../../../shared/language"; import Modal from "../../atoms/modal/modal"; import ItemTextListView from "../../molecules/item-text-list-view/item-text-list-view"; @@ -12,10 +13,7 @@ interface IModalDropAreaFilesAddedProps { const ModalDropAreaFilesAdded: React.FunctionComponent = (props) => { const settings = useGlobalSettings(); - const MessageFilesAdded = new Language(settings.language).text( - "Deze bestanden zijn toegevoegd", - "These files have been added" - ); + const MessageFilesAdded = new Language(settings.language).key(localization.MessageFilesAdded); return ( = memo((props) => // content const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessagePrevious = language.text("Vorige", "Previous"); - const MessageNext = language.text("Volgende", "Next"); + const MessagePrevious = language.key(localization.MessagePrevious); + const MessageNext = language.key(localization.MessageNext); // used for reading current location const history = useLocation(); diff --git a/starsky/starsky/clientapp/src/components/organisms/application-exception/application-exception.tsx b/starsky/starsky/clientapp/src/components/organisms/application-exception/application-exception.tsx index e44fa5c8cc..9163df282a 100644 --- a/starsky/starsky/clientapp/src/components/organisms/application-exception/application-exception.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/application-exception/application-exception.tsx @@ -1,19 +1,14 @@ import { FunctionComponent } from "react"; import useGlobalSettings from "../../../hooks/use-global-settings"; +import localization from "../../../localization/localization.json"; import { Language } from "../../../shared/language"; import MenuDefault from "../menu-default/menu-default"; const ApplicationException: FunctionComponent = () => { const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageApplicationException = language.text( - "We hebben een op dit moment een verstoring op de applicatie", - "We have a disruption on the application right now" - ); - const MessageRefreshPageTryAgain = language.text( - "Herlaad de applicatie om het opnieuw te proberen", - "Please reload the application to try again" - ); + const MessageApplicationException = language.key(localization.MessageApplicationException); + const MessageRefreshPageTryAgain = language.key(localization.MessageRefreshPageTryAgain); return ( <> diff --git a/starsky/starsky/clientapp/src/components/organisms/archive-sidebar/archive-sidebar.tsx b/starsky/starsky/clientapp/src/components/organisms/archive-sidebar/archive-sidebar.tsx index 5d8cd3d7d8..866bec7b0e 100644 --- a/starsky/starsky/clientapp/src/components/organisms/archive-sidebar/archive-sidebar.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/archive-sidebar/archive-sidebar.tsx @@ -2,6 +2,7 @@ import React, { memo, useEffect, useLayoutEffect } from "react"; import useGlobalSettings from "../../../hooks/use-global-settings"; import useLocation from "../../../hooks/use-location/use-location"; import { PageType } from "../../../interfaces/IDetailView"; +import localization from "../../../localization/localization.json"; import { Language } from "../../../shared/language"; import { URLPath } from "../../../shared/url-path"; import ArchiveSidebarColorClass from "../../molecules/archive-sidebar/archive-sidebar-color-class"; @@ -13,10 +14,11 @@ const ArchiveSidebar: React.FunctionComponent = memo((arch // Content const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageSelectionName = language.text("Selectie", "Selection"); - const MessageReadOnlyFolder = language.text("Alleen lezen map", "Read only folder"); - const MessageUpdateLabels = language.text("Labels wijzigingen", "Update labels"); - const MessageColorClassification = language.text("Kleur-Classificatie", "Color Classification"); + + const MessageSelectionName = language.key(localization.MessageSelectionName); + const MessageReadOnlyFolder = language.key(localization.MessageReadOnlyFolder); + const MessageUpdateLabels = language.key(localization.MessageUpdateLabels); + const MessageColorClassification = language.key(localization.MessageColorClassification); // Update view based on url parameters const history = useLocation(); diff --git a/starsky/starsky/clientapp/src/components/organisms/detail-view-sidebar/detail-view-sidebar.tsx b/starsky/starsky/clientapp/src/components/organisms/detail-view-sidebar/detail-view-sidebar.tsx index 80f54bda58..026a40aaa2 100644 --- a/starsky/starsky/clientapp/src/components/organisms/detail-view-sidebar/detail-view-sidebar.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/detail-view-sidebar/detail-view-sidebar.tsx @@ -7,6 +7,7 @@ import useLocation from "../../../hooks/use-location/use-location"; import { IDetailView } from "../../../interfaces/IDetailView"; import { IExifStatus } from "../../../interfaces/IExifStatus"; import { IFileIndexItem } from "../../../interfaces/IFileIndexItem"; +import localization from "../../../localization/localization.json"; import { AsciiNull } from "../../../shared/ascii-null"; import AspectRatio from "../../../shared/aspect-ratio"; import BytesFormat from "../../../shared/bytes-format"; @@ -41,25 +42,15 @@ const DetailViewSidebar: React.FunctionComponent = memo // content const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageTitleName = language.text("Titel", "Title"); - const MessageInfoName = "Info"; - const MessageColorClassification = language.text("Kleur-Classificatie", "Color Classification"); - const MessageDateTimeAgoEdited = language.text("geleden bewerkt", "ago edited"); - const MessageDateLessThan1Minute = language.text( - "minder dan één minuut", - "less than one minute" - ); - const MessageDateMinutes = language.text("minuten", "minutes"); - const MessageDateHour = language.text("uur", "hour"); - - const MessageCopiedLabels = language.text( - "De labels zijn gekopieerd", - "The labels have been copied" - ); - const MessagePasteLabels = language.text( - "De labels zijn overschreven", - "The labels have been overwritten" - ); + const MessageTitleName = language.key(localization.MessageTitleName); + const MessageInfoName = language.key(localization.MessageInfoName); + const MessageColorClassification = language.key(localization.MessageColorClassification); + const MessageDateTimeAgoEdited = language.key(localization.MessageDateTimeAgoEdited); + const MessageDateLessThan1Minute = language.key(localization.MessageDateLessThan1Minute); + const MessageDateMinutes = language.key(localization.MessageDateMinutes); + const MessageDateHour = language.key(localization.MessageDateHour); + const MessageCopiedLabels = language.key(localization.MessageCopiedLabels); + const MessagePasteLabels = language.key(localization.MessagePasteLabels); const history = useLocation(); diff --git a/starsky/starsky/clientapp/src/components/organisms/detailview-info-datetime/detailview-info-datetime.tsx b/starsky/starsky/clientapp/src/components/organisms/detailview-info-datetime/detailview-info-datetime.tsx index 6ff8b355d4..4add557d31 100644 --- a/starsky/starsky/clientapp/src/components/organisms/detailview-info-datetime/detailview-info-datetime.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/detailview-info-datetime/detailview-info-datetime.tsx @@ -2,6 +2,7 @@ import React, { memo } from "react"; import { DetailViewAction } from "../../../contexts/detailview-context"; import useGlobalSettings from "../../../hooks/use-global-settings"; import { IFileIndexItem } from "../../../interfaces/IFileIndexItem"; +import localization from "../../../localization/localization.json"; import { isValidDate, parseDate, parseTime } from "../../../shared/date"; import { Language } from "../../../shared/language"; import ModalDatetime from "../modal-edit-date-time/modal-edit-datetime"; @@ -17,10 +18,9 @@ const DetailViewInfoDateTime: React.FunctionComponent { const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageCreationDate = language.text("Aanmaakdatum", "Creation date"); - const MessageCreationDateUnknownTime = language.text( - "is op een onbekend moment", - "is at an unknown time" + const MessageCreationDate = language.key(localization.MessageCreationDate); + const MessageCreationDateIsAtUnknownTime = language.key( + localization.MessageCreationDateIsAtUnknownTime ); const [modalDatetimeOpen, setModalDatetimeOpen] = React.useState(false); @@ -70,7 +70,7 @@ const DetailViewInfoDateTime: React.FunctionComponent {MessageCreationDate} -

{MessageCreationDateUnknownTime}

+

{MessageCreationDateIsAtUnknownTime}

) : null} diff --git a/starsky/starsky/clientapp/src/components/organisms/detailview-info-location/detailview-info-location.tsx b/starsky/starsky/clientapp/src/components/organisms/detailview-info-location/detailview-info-location.tsx index b865dc7e15..763e1aefd9 100644 --- a/starsky/starsky/clientapp/src/components/organisms/detailview-info-location/detailview-info-location.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/detailview-info-location/detailview-info-location.tsx @@ -4,6 +4,7 @@ import useGlobalSettings from "../../../hooks/use-global-settings"; import useLocation from "../../../hooks/use-location/use-location"; import { IFileIndexItem } from "../../../interfaces/IFileIndexItem"; import { IGeoLocationModel } from "../../../interfaces/IGeoLocationModel"; +import localization from "../../../localization/localization.json"; import { Language } from "../../../shared/language"; import ModalGeo from "../modal-geo/modal-geo"; @@ -33,10 +34,9 @@ const DetailViewInfoLocation: React.FunctionComponent = ({ state, dispatch const language = new Language(settings.language); // Content - const MessageSelectAction = language.text("Selecteer", "Select"); + const MessageSelectAction = language.key(localization.MessageSelectAction); // Selection const history = useLocation(); diff --git a/starsky/starsky/clientapp/src/components/organisms/menu-trash/menu-trash.tsx b/starsky/starsky/clientapp/src/components/organisms/menu-trash/menu-trash.tsx index 74ecea8109..2f0911f477 100644 --- a/starsky/starsky/clientapp/src/components/organisms/menu-trash/menu-trash.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/menu-trash/menu-trash.tsx @@ -4,6 +4,7 @@ import useGlobalSettings from "../../../hooks/use-global-settings"; import useHotKeys from "../../../hooks/use-keyboard/use-hotkeys"; import useLocation from "../../../hooks/use-location/use-location"; import { IArchiveProps } from "../../../interfaces/IArchiveProps"; +import localization from "../../../localization/localization.json"; import FetchPost from "../../../shared/fetch/fetch-post"; import { FileListCache } from "../../../shared/filelist-cache"; import { Language } from "../../../shared/language"; @@ -12,6 +13,8 @@ import { Select } from "../../../shared/select"; import { URLPath } from "../../../shared/url-path"; import { UrlQuery } from "../../../shared/url-query"; import HamburgerMenuToggle from "../../atoms/hamburger-menu-toggle/hamburger-menu-toggle"; +import MenuOptionModal from "../../atoms/menu-option-modal/menu-option-modal.tsx"; +import MenuOption from "../../atoms/menu-option/menu-option.tsx"; import MoreMenu from "../../atoms/more-menu/more-menu"; import Preloader from "../../atoms/preloader/preloader"; import MenuSearchBar from "../../molecules/menu-inline-search/menu-inline-search"; @@ -20,9 +23,6 @@ import { MenuOptionSelectionUndo } from "../../molecules/menu-option-selection-u import { MenuSelectCount } from "../../molecules/menu-select-count/menu-select-count"; import ModalForceDelete from "../modal-force-delete/modal-force-delete"; import NavContainer from "../nav-container/nav-container"; -import MenuOption from "../../atoms/menu-option/menu-option.tsx"; -import localization from "../../../localization/localization.json"; -import MenuOptionModal from "../../atoms/menu-option-modal/menu-option-modal.tsx"; interface IMenuTrashProps { state: IArchiveProps; @@ -34,7 +34,7 @@ const MenuTrash: React.FunctionComponent = ({ state, dispatch } const language = new Language(settings.language); // Content - const MessageSelectAction = language.text("Selecteer", "Select"); + const MessageSelectAction = language.key(localization.MessageSelectAction); const [hamburgerMenu, setHamburgerMenu] = React.useState(false); const [isLoading, setIsLoading] = React.useState(false); diff --git a/starsky/starsky/clientapp/src/components/organisms/modal-archive-mkdir/modal-archive-mkdir.tsx b/starsky/starsky/clientapp/src/components/organisms/modal-archive-mkdir/modal-archive-mkdir.tsx index 8a012dbab7..708ca5f1ba 100644 --- a/starsky/starsky/clientapp/src/components/organisms/modal-archive-mkdir/modal-archive-mkdir.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/modal-archive-mkdir/modal-archive-mkdir.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { ArchiveAction } from "../../../contexts/archive-context"; import useGlobalSettings from "../../../hooks/use-global-settings"; import { IArchiveProps } from "../../../interfaces/IArchiveProps"; +import localization from "../../../localization/localization.json"; import { CastToInterface } from "../../../shared/cast-to-interface"; import FetchGet from "../../../shared/fetch/fetch-get"; import FetchPost from "../../../shared/fetch/fetch-post"; @@ -27,19 +28,10 @@ const ModalArchiveMkdir: React.FunctionComponent = ({ // content const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageFeatureName = language.text("Nieuwe map aanmaken", "Create new folder"); - const MessageNonValidDirectoryName = language.text( - "Controleer de naam, deze map kan niet zo worden aangemaakt", - "Check the name, this folder cannot be created in this way" - ); - const MessageGeneralMkdirCreateError = language.text( - "Er is misgegaan met het aanmaken van deze map", - "An error occurred while creating this folder" - ); - const MessageDirectoryExistError = language.text( - "De map bestaat al, probeer een andere naam", - "The folder already exists, try a different name" - ); + const MessageCreateNewFolderFeature = language.key(localization.MessageCreateNewFolderFeature); + const MessageNonValidDirectoryName = language.key(localization.MessageNonValidDirectoryName); + const MessageGeneralMkdirCreateError = language.key(localization.MessageGeneralMkdirCreateError); + const MessageDirectoryExistError = language.key(localization.MessageDirectoryExistError); // to show errors const useErrorHandler = (initialState: string | null) => { @@ -124,7 +116,7 @@ const ModalArchiveMkdir: React.FunctionComponent = ({ }} >
-
{MessageFeatureName}
+
{MessageCreateNewFolderFeature}
= ({ data-test="modal-archive-mkdir-btn-default" onClick={pushRenameChange} > - {isLoading ? "Loading..." : MessageFeatureName} + {isLoading ? "Loading..." : MessageCreateNewFolderFeature}
diff --git a/starsky/starsky/clientapp/src/components/organisms/modal-archive-rename/modal-archive-rename.tsx b/starsky/starsky/clientapp/src/components/organisms/modal-archive-rename/modal-archive-rename.tsx index 8122916938..5478f36579 100644 --- a/starsky/starsky/clientapp/src/components/organisms/modal-archive-rename/modal-archive-rename.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/modal-archive-rename/modal-archive-rename.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { ArchiveAction } from "../../../contexts/archive-context"; import useGlobalSettings from "../../../hooks/use-global-settings"; import useLocation from "../../../hooks/use-location/use-location"; +import localization from "../../../localization/localization.json"; import FetchPost from "../../../shared/fetch/fetch-post"; import { FileExtensions } from "../../../shared/file-extensions"; import { FileListCache } from "../../../shared/filelist-cache"; @@ -21,15 +22,11 @@ const ModalArchiveRename: React.FunctionComponent = (pr // content const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageRenameFolder = language.text("Huidige mapnaam wijzigen", "Rename current folder"); - const MessageNonValidDirectoryName: string = language.text( - "Deze mapnaam is niet valide", - "Directory name is not valid" - ); - const MessageGeneralError: string = language.text( - "Er is iets misgegaan met de aanvraag, probeer het later opnieuw", - "Something went wrong with the request, please try again later" + const MessageRenameFolder = language.key(localization.MessageRenameCurrentFolder); + const MessageNonValidDirectoryName: string = language.key( + localization.MessageNonValidDirectoryName ); + const MessageGeneralError: string = language.key(localization.MessageErrorGenericFail); // to show errors const useErrorHandler = (initialState: string | null) => { diff --git a/starsky/starsky/clientapp/src/components/organisms/modal-archive-synchronize-manually/modal-archive-synchronize-manually.tsx b/starsky/starsky/clientapp/src/components/organisms/modal-archive-synchronize-manually/modal-archive-synchronize-manually.tsx index 7af03621bc..6cc1a0d4ac 100644 --- a/starsky/starsky/clientapp/src/components/organisms/modal-archive-synchronize-manually/modal-archive-synchronize-manually.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/modal-archive-synchronize-manually/modal-archive-synchronize-manually.tsx @@ -4,6 +4,7 @@ import useGlobalSettings from "../../../hooks/use-global-settings"; import useInterval from "../../../hooks/use-interval"; import useLocation from "../../../hooks/use-location/use-location"; import { IArchiveProps } from "../../../interfaces/IArchiveProps"; +import localization from "../../../localization/localization.json"; import { CastToInterface } from "../../../shared/cast-to-interface"; import FetchGet from "../../../shared/fetch/fetch-get"; import FetchPost from "../../../shared/fetch/fetch-post"; @@ -27,32 +28,13 @@ const ModalArchiveSynchronizeManually: React.FunctionComponent // content const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageNonValidExtension: string = language.text( - "Dit bestand kan zo niet worden weggeschreven", - "This file cannot be saved" + const MessageNonValidExtension = language.key(localization.MessageNonValidExtension); + const MessageChangeToDifferentExtension = language.key( + localization.MessageChangeToDifferentExtension ); - const MessageChangeToDifferentExtension = language.text( - "Let op! Je veranderd de extensie van het bestand, " + "deze kan hierdoor onleesbaar worden", - "Pay attention! You change the file extension, which can make it unreadable" - ); - const MessageGeneralError: string = language.text( - "Er is iets misgegaan met de aanvraag, probeer het later opnieuw", - "Something went wrong with the request, please try again later" - ); - const MessageRenameFileName = language.text("Bestandsnaam wijzigen", "Rename file name"); + const MessageRenameServerError = language.key(localization.MessageRenameServerError); + const MessageRenameFileName = language.key(localization.MessageRenameFileName); // Fallback for no context if (!state) { @@ -130,7 +124,7 @@ const ModalDetailviewRenameFile: React.FunctionComponent const result = await FetchPost(new UrlQuery().UrlDiskRename(), bodyParams.toString()); if (result.statusCode !== 200) { - setError(MessageGeneralError); + setError(MessageRenameServerError); // and renewable setIsLoading(false); setIsFormEnabled(true); diff --git a/starsky/starsky/clientapp/src/components/organisms/modal-display-options/modal-display-options.tsx b/starsky/starsky/clientapp/src/components/organisms/modal-display-options/modal-display-options.tsx index 7f8fccda4f..91e73c301d 100644 --- a/starsky/starsky/clientapp/src/components/organisms/modal-display-options/modal-display-options.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/modal-display-options/modal-display-options.tsx @@ -2,6 +2,7 @@ import React, { useEffect } from "react"; import useGlobalSettings from "../../../hooks/use-global-settings"; import useLocation from "../../../hooks/use-location/use-location"; import { SortType } from "../../../interfaces/IArchive"; +import localization from "../../../localization/localization.json"; import { Language } from "../../../shared/language"; import { URLPath } from "../../../shared/url-path"; import Modal from "../../atoms/modal/modal"; @@ -17,13 +18,25 @@ const ModalDisplayOptions: React.FunctionComponent = // content const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageDisplayOptions = language.text("Weergave opties", "Display options"); - const MessageSwitchButtonCollectionsOff = language.text("Toon raw bestanden", "Show raw files"); - const MessageSwitchButtonCollectionsOn = language.text("Verberg raw bestanden", "Hide Raw files"); - const MessageSwitchButtonIsSingleItemOff = language.text("Alles inladen", "Load everything"); - const MessageSwitchButtonIsSingleItemOn = language.text("Klein inladen", "Small loading"); - const MessageSwitchButtonIsSocketOn = language.text("Realtime updates", "Realtime updates"); - const MessageSwitchButtonIsSocketOff = language.text("Ververs zelf", "Refresh yourself"); + const MessageDisplayOptions = language.key(localization.MessageDisplayOptions); + const MessageDisplayOptionsSwitchButtonCollectionsOff = language.key( + localization.MessageDisplayOptionsSwitchButtonCollectionsOff + ); + const MessageDisplayOptionsSwitchButtonCollectionsOn = language.key( + localization.MessageDisplayOptionsSwitchButtonCollectionsOn + ); + const MessageDisplayOptionsSwitchButtonIsSingleItemOff = language.key( + localization.MessageDisplayOptionsSwitchButtonIsSingleItemOff + ); + const MessageDisplayOptionsSwitchButtonIsSingleItemOn = language.key( + localization.MessageDisplayOptionsSwitchButtonIsSingleItemOn + ); + const MessageDisplayOptionsSwitchButtonIsSocketOn = language.key( + localization.MessageDisplayOptionsSwitchButtonIsSocketOn + ); + const MessageDisplayOptionsSwitchButtonIsSocketOff = language.key( + localization.MessageDisplayOptionsSwitchButtonIsSocketOff + ); const history = useLocation(); @@ -98,9 +111,9 @@ const ModalDisplayOptions: React.FunctionComponent = isOn={!collections} data-test="toggle-collections" isEnabled={true} - leftLabel={MessageSwitchButtonCollectionsOn} + leftLabel={MessageDisplayOptionsSwitchButtonCollectionsOn} onToggle={() => toggleCollections()} - rightLabel={MessageSwitchButtonCollectionsOff} + rightLabel={MessageDisplayOptionsSwitchButtonCollectionsOff} />
@@ -108,8 +121,8 @@ const ModalDisplayOptions: React.FunctionComponent = data-test="toggle-slow-files" isOn={isAlwaysLoadImage} isEnabled={true} - leftLabel={MessageSwitchButtonIsSingleItemOn} - rightLabel={MessageSwitchButtonIsSingleItemOff} + leftLabel={MessageDisplayOptionsSwitchButtonIsSingleItemOn} + rightLabel={MessageDisplayOptionsSwitchButtonIsSingleItemOff} onToggle={() => toggleSlowFiles()} />
@@ -118,9 +131,9 @@ const ModalDisplayOptions: React.FunctionComponent = isOn={isUseSockets} data-test="toggle-sockets" isEnabled={true} - leftLabel={MessageSwitchButtonIsSocketOn} + leftLabel={MessageDisplayOptionsSwitchButtonIsSocketOn} onToggle={() => toggleSockets()} - rightLabel={MessageSwitchButtonIsSocketOff} + rightLabel={MessageDisplayOptionsSwitchButtonIsSocketOff} />
diff --git a/starsky/starsky/clientapp/src/components/organisms/modal-download/modal-download.tsx b/starsky/starsky/clientapp/src/components/organisms/modal-download/modal-download.tsx index 5f6e908b2a..f55fb74f2d 100644 --- a/starsky/starsky/clientapp/src/components/organisms/modal-download/modal-download.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/modal-download/modal-download.tsx @@ -2,6 +2,7 @@ import React, { useEffect } from "react"; import useFetch from "../../../hooks/use-fetch"; import useGlobalSettings from "../../../hooks/use-global-settings"; import useInterval from "../../../hooks/use-interval"; +import localization from "../../../localization/localization.json"; import { ExportIntervalUpdate } from "../../../shared/export/export-interval-update"; import FetchPost from "../../../shared/fetch/fetch-post"; import { FileExtensions } from "../../../shared/file-extensions"; @@ -32,25 +33,13 @@ const ModalDownload: React.FunctionComponent = (props) => { // content const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageDownloadSelection = language.text("Download selectie", "Download selection"); - const MessageOriginalFile = language.text("Origineel bestand", "Original file"); - const MessageThumbnailFile = "Thumbnail"; - const MessageGenericExportFail = language.text( - "Er is iets misgegaan met exporteren", - "Something went wrong with exporting" - ); - const MessageExportReady = language.text( - "Het bestand {createZipKey} is klaar met exporteren.", - "The file {createZipKey} has finished exporting." - ); - const MessageDownloadAsZipArchive = language.text( - "Download als zip-archief", - "Download as a zip archive" - ); - const MessageOneMomentPlease = language.text( - "Een moment geduld alstublieft", - "One moment please" - ); + const MessageDownloadSelection = language.key(localization.MessageDownloadSelection); + const MessageOriginalFile = language.key(localization.MessageOriginalFile); + const MessageThumbnailFile = language.key(localization.MessageThumbnailFile); + const MessageGenericExportFail = language.key(localization.MessageGenericExportFail); + const MessageExportReady = language.key(localization.MessageExportReady); + const MessageDownloadAsZipArchive = language.key(localization.MessageDownloadAsZipArchive); + const MessageOneMomentPlease = language.key(localization.MessageOneMomentPlease); const [isProcessing, setIsProcessing] = React.useState(ProcessingState.default); const [createZipKey, setCreateZipKey] = React.useState(""); diff --git a/starsky/starsky/clientapp/src/components/organisms/modal-edit-date-time/modal-edit-datetime.tsx b/starsky/starsky/clientapp/src/components/organisms/modal-edit-date-time/modal-edit-datetime.tsx index 932b654468..6effd32c61 100644 --- a/starsky/starsky/clientapp/src/components/organisms/modal-edit-date-time/modal-edit-datetime.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/modal-edit-date-time/modal-edit-datetime.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; import useGlobalSettings from "../../../hooks/use-global-settings"; import { IFileIndexItem } from "../../../interfaces/IFileIndexItem"; +import localization from "../../../localization/localization.json"; import { isValidDate, leftPad, @@ -28,15 +29,12 @@ const ModalEditDatetime: React.FunctionComponent = (props) // content const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageModalDatetime = language.text("Datum en tijd bewerken", "Edit date and time"); - const MessageYear = language.text("Jaar", "Year"); - const MessageMonth = language.text("Maand", "Month"); - const MessageDate = language.text("Dag", "Day"); - const MessageTime = language.text("Tijd", "Time"); - const MessageErrorDatetime = language.text( - "De datum en tijd zijn incorrect ingegeven", - "The date and time were entered incorrectly" - ); + const MessageModalDatetime = language.key(localization.MessageModalDatetime); + const MessageYear = language.key(localization.MessageYear); + const MessageMonth = language.key(localization.MessageMonth); + const MessageDate = language.key(localization.MessageDate); + const MessageTime = language.key(localization.MessageTime); + const MessageErrorDatetime = language.key(localization.MessageErrorDatetime); const isFormEnabled = true; diff --git a/starsky/starsky/clientapp/src/components/organisms/modal-force-delete/modal-force-delete.tsx b/starsky/starsky/clientapp/src/components/organisms/modal-force-delete/modal-force-delete.tsx index a7d05f92ad..a26f03ff1e 100644 --- a/starsky/starsky/clientapp/src/components/organisms/modal-force-delete/modal-force-delete.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/modal-force-delete/modal-force-delete.tsx @@ -2,6 +2,7 @@ import { ArchiveAction } from "../../../contexts/archive-context"; import useGlobalSettings from "../../../hooks/use-global-settings"; import useLocation from "../../../hooks/use-location/use-location"; import { IArchiveProps } from "../../../interfaces/IArchiveProps"; +import localization from "../../../localization/localization.json"; import FetchPost from "../../../shared/fetch/fetch-post"; import { Language } from "../../../shared/language"; import { ClearSearchCache } from "../../../shared/search/clear-search-cache"; @@ -32,12 +33,10 @@ const ModalForceDelete: React.FunctionComponent = ({ // content const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageDeleteIntroText = language.text( - "Weet je zeker dat je dit bestand wilt verwijderen van alle devices?", - "Are you sure you want to delete this file from all devices?" - ); - const MessageCancel = language.text("Annuleren", "Cancel"); - const MessageDeleteImmediately = language.text("Verwijder onmiddellijk", "Delete immediately"); + const MessageDeleteIntroText = language.key(localization.MessageDeleteIntroText); + const MessageCancel = language.key(localization.MessageCancel); + const MessageDeleteImmediately = language.key(localization.MessageDeleteImmediately); + const history = useLocation(); const undoSelection = () => new Select(select, setSelect, state, history).undoSelection(); diff --git a/starsky/starsky/clientapp/src/components/organisms/modal-publish/modal-publish.tsx b/starsky/starsky/clientapp/src/components/organisms/modal-publish/modal-publish.tsx index 8a754e8f9c..a5a248e5bf 100644 --- a/starsky/starsky/clientapp/src/components/organisms/modal-publish/modal-publish.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/modal-publish/modal-publish.tsx @@ -2,6 +2,7 @@ import React, { useEffect } from "react"; import useFetch from "../../../hooks/use-fetch"; import useGlobalSettings from "../../../hooks/use-global-settings"; import useInterval from "../../../hooks/use-interval"; +import localization from "../../../localization/localization.json"; import { ExportIntervalUpdate } from "../../../shared/export/export-interval-update"; import { ProcessingState } from "../../../shared/export/processing-state"; import FetchGet from "../../../shared/fetch/fetch-get"; @@ -27,35 +28,17 @@ const ModalPublish: React.FunctionComponent = (props) => { // content const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessagePublishSelection = language.text("Publiceer selectie", "Publish selection"); - const MessageGenericExportFail = language.text( - "Er is iets misgegaan met exporteren", - "Something went wrong with exporting" - ); - const MessageRetryExportFail = language.text("Probeer het opnieuw", "Retry this"); - - const MessageExportReady = language.text( - "Het bestand {createZipKey} is klaar met exporteren.", - "The file {createZipKey} has finished exporting." - ); - const MessageDownloadAsZipArchive = language.text( - "Download als zip-archief", - "Download as a zip archive" - ); - const MessageOneMomentPlease = language.text( - "Een moment geduld alstublieft", - "One moment please" - ); - const MessageItemName = language.text("Waar gaat het item over?", "What is the item about?"); - const MessageItemNameInUse = language.text( - "Deze naam is al in gebruik, kies een andere naam", - "This name is already in use, please choose another name" - ); - const MessagePublishProfileName = language.text("Profiel instelling", "Profile setting"); - - const MessagePublishProfileNamesErrored = language.text( - "Profiel instelling: {publishProfileNames} bevat bestand locatie fouten", - "Profile setting: {publishProfileNames} contains filepath errors" + const MessagePublishSelection = language.key(localization.MessagePublishSelection); + const MessageGenericExportFail = language.key(localization.MessageGenericExportFail); + const MessageRetryExportFail = language.key(localization.MessageRetryExportFail); + const MessageExportReady = language.key(localization.MessageExportReady); + const MessageDownloadAsZipArchive = language.key(localization.MessageDownloadAsZipArchive); + const MessageOneMomentPlease = language.key(localization.MessageOneMomentPlease); + const MessageItemName = language.key(localization.MessageItemName); + const MessageItemNameInUse = language.key(localization.MessageItemNameInUse); + const MessagePublishProfileName = language.key(localization.MessagePublishProfileName); + const MessagePublishProfileNamesErrored = language.key( + localization.MessagePublishProfileNamesErrored ); const [isProcessing, setIsProcessing] = React.useState(ProcessingState.default); diff --git a/starsky/starsky/clientapp/src/containers/account-register.tsx b/starsky/starsky/clientapp/src/containers/account-register.tsx index 453ec9fafd..a63462235b 100644 --- a/starsky/starsky/clientapp/src/containers/account-register.tsx +++ b/starsky/starsky/clientapp/src/containers/account-register.tsx @@ -23,37 +23,13 @@ const AccountRegister: FunctionComponent = () => { const MessageConfirmPassword = language.key(localization.MessageConfirmPassword); const MessageNoUsernamePassword = language.key(localization.MessageNoUsernamePassword); const MessageWrongFormatEmailAddress = language.key(localization.MessageWrongFormatEmailAddress); - const MessagePasswordToShort = language.text( - "Gebruik minimaal 8 tekens voor je wachtwoord", - "Use at least 8 characters for your password" - ); - const MessagePasswordNoMatch = language.text( - "Deze wachtwoorden komen niet overeen. Probeer het opnieuw", - "These passwords do not match. Please try again" - ); - const MessageConnection = language.text( - "Er is geen verbinding mogelijk, probeer het later opnieuw", - "No connection is possible, please try again later" - ); - const MessageRejectedBadRequest = language.text( - "Dit verzoek is afgewezen aangezien er niet voldaan is aan de beveiligingseisen (Error 400)", - "This request was rejected because the security requirements were not met (Error 400)" - ); - const MessageRegistrationTurnedOff = language.text( - "Registratie is uitgezet", - "Registration is turned off" - ); - const MessageSignInInstead = language.text("In plaats daarvan inloggen", "Sign in instead"); - - const MessageLegalCreateAccountHtml = language.text( - `Door het creΓ«ren van een account gaat u akkoord met de - Algemene Voorwaarden van Starsky. Raadpleeg en bekijk hier onze - Privacykennisgeving en onze - Cookieverklaring.`, - `By creating an account you agree to Starsky's Conditions of Use. - Please see our Privacy Notice and our - Cookies Notice ` - ); + const MessagePasswordToShort = language.key(localization.MessagePasswordToShort); + const MessagePasswordNoMatch = language.key(localization.MessagePasswordNoMatch); + const MessageConnection = language.key(localization.MessageConnection); + const MessageRejectedBadRequest = language.key(localization.MessageRejectedBadRequest); + const MessageRegistrationTurnedOff = language.key(localization.MessageRegistrationTurnedOff); + const MessageSignInInstead = language.key(localization.MessageSignInInstead); + const MessageLegalCreateAccountHtml = language.key(localization.MessageLegalCreateAccountHtml); const history = useLocation(); diff --git a/starsky/starsky/clientapp/src/containers/login.tsx b/starsky/starsky/clientapp/src/containers/login.tsx index 2c55d7775e..7c9e517887 100755 --- a/starsky/starsky/clientapp/src/containers/login.tsx +++ b/starsky/starsky/clientapp/src/containers/login.tsx @@ -4,6 +4,7 @@ import Preloader from "../components/atoms/preloader/preloader"; import useFetch from "../hooks/use-fetch"; import useGlobalSettings from "../hooks/use-global-settings"; import useLocation from "../hooks/use-location/use-location"; +import localization from "../localization/localization.json"; import { BrowserDetect } from "../shared/browser-detect"; import { DocumentTitle } from "../shared/document-title"; import FetchPost from "../shared/fetch/fetch-post"; @@ -30,40 +31,21 @@ export const Login: React.FC = () => { const language = new Language(settings.language); const MessageApplicationName = "Starsky"; - const MessageWrongUsernamePassword = language.text( - "Je gebruikersnaam of wachtwoord is niet juist. Probeer het opnieuw", - "Your username or password is incorrect. Try again" - ); - const MessageLockedOut = language.text( - "Je hebt te vaak geprobeerd in te loggen, probeer het over een uur nog een keer", - "You've tried to login too many times, please try again in an hour " - ); - - const MessageNoUsernamePassword = language.text( - "Voer een emailadres en een wachtwoord in", - "Enter an email address and password" - ); - const MessageWrongFormatEmailAddress = language.text( - "Controleer je email adres", - "Check your email address" - ); - const MessageUsername = language.text("E-mailadres", "E-mail address"); - const MessageConnection = language.text( - "Er is geen verbinding mogelijk, probeer het later opnieuw", - "No connection is possible, please try again later" - ); - const MessageDatabaseConnection = language.text( - "Er zijn problemen met de verbinding met de database. Controleer en pas de appsettings aan", - "There are database connection issues. Check and edit the appsettings" - ); - const LogoutWarning = language.text("Wil je uitloggen?", "Do you want to log out?"); - const MessageStayLoggedIn = language.text("Blijf ingelogd", "Stay logged in"); - const MessagePassword = language.text("Geef je wachtwoord op", "Enter your password"); - const MessageExamplePassword = language.text("superveilig", "supersafe"); - const MessageExampleUsername = "dont@mail.me"; - const MessageLogin = language.text("Inloggen", "Login"); - const MessageLogout = language.text("Uitloggen", "Logout"); - const MessageCreateAccount = language.text("Account maken", "Create account"); + const MessageWrongUsernamePassword = language.key(localization.MessageWrongUsernamePassword); + const MessageLockedOut = language.key(localization.MessageLockedOut); + const MessageNoUsernamePassword = language.key(localization.MessageNoUsernamePassword); + const MessageWrongFormatEmailAddress = language.key(localization.MessageWrongFormatEmailAddress); + const MessageUsername = language.key(localization.MessageUsername); + const MessageConnection = language.key(localization.MessageConnection); + const MessageDatabaseConnection = language.key(localization.MessageDatabaseConnection); + const LogoutWarning = language.key(localization.LogoutWarning); + const MessageStayLoggedIn = language.key(localization.MessageStayLoggedIn); + const MessagePassword = language.key(localization.MessagePassword); + const MessageExamplePassword = language.key(localization.MessageExamplePassword); + const MessageExampleUsername = language.key(localization.MessageExampleUsername); + const MessageLogin = language.key(localization.MessageLogin); + const MessageLogout = language.key(localization.MessageLogout); + const MessageCreateAccount = language.key(localization.MessageCreateAccount); // We don't want to login twice const [isLogin, setIsLogin] = React.useState(true); diff --git a/starsky/starsky/clientapp/src/containers/media-content.tsx b/starsky/starsky/clientapp/src/containers/media-content.tsx index 65d38b993f..6103fe9f3b 100644 --- a/starsky/starsky/clientapp/src/containers/media-content.tsx +++ b/starsky/starsky/clientapp/src/containers/media-content.tsx @@ -10,6 +10,7 @@ import useGlobalSettings from "../hooks/use-global-settings"; import useLocation from "../hooks/use-location/use-location"; import { IArchive } from "../interfaces/IArchive"; import { IDetailView, PageType } from "../interfaces/IDetailView"; +import localization from "../localization/localization.json"; import { NotFoundPage } from "../pages/not-found-page"; import { Language } from "../shared/language"; import { Login } from "./login"; @@ -27,10 +28,8 @@ const MediaContent: React.FC = () => { const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageConnectionRealtimeError = language.text( - "De verbinding is niet helemaal okΓ©. We proberen het te herstellen", - "The connection is not quite right. We are trying to fix it" - ); + const MessageConnectionRealtimeError = language.key(localization.MessageConnectionRealtimeError); + const MessageApplicationFailed = language.key(localization.MessageApplicationFailed); console.log(`-----------------MediaContent ${pageType} (rendered again)-------------------`); @@ -38,7 +37,7 @@ const MediaContent: React.FC = () => { return ( <>
- The application has failed. Please reload it to try it again + {MessageApplicationFailed} ); } diff --git a/starsky/starsky/clientapp/src/containers/preferences/preferences.tsx b/starsky/starsky/clientapp/src/containers/preferences/preferences.tsx index f7b0dde01e..b5f9a2610a 100644 --- a/starsky/starsky/clientapp/src/containers/preferences/preferences.tsx +++ b/starsky/starsky/clientapp/src/containers/preferences/preferences.tsx @@ -3,12 +3,13 @@ import PreferencesAppSettings from "../../components/organisms/preferences-app-s import PreferencesPassword from "../../components/organisms/preferences-password/preferences-password"; import PreferencesUsername from "../../components/organisms/preferences-username/preferences-username"; import useGlobalSettings from "../../hooks/use-global-settings"; +import localization from "../../localization/localization.json"; import { Language } from "../../shared/language"; export const Preferences: React.FunctionComponent = () => { const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessagePreferences = language.text("Voorkeuren", "Preferences"); + const MessagePreferences = language.key(localization.MessagePreferences); return ( <> diff --git a/starsky/starsky/clientapp/src/containers/search.tsx b/starsky/starsky/clientapp/src/containers/search.tsx index 73edfe22ec..e6d706e84d 100644 --- a/starsky/starsky/clientapp/src/containers/search.tsx +++ b/starsky/starsky/clientapp/src/containers/search.tsx @@ -6,6 +6,7 @@ import ArchiveSidebar from "../components/organisms/archive-sidebar/archive-side import useGlobalSettings from "../hooks/use-global-settings"; import useLocation from "../hooks/use-location/use-location"; import { IArchiveProps } from "../interfaces/IArchiveProps"; +import localization from "../localization/localization.json"; import { Language } from "../shared/language"; import { URLPath } from "../shared/url-path"; import MenuMenuSearchContainer from "./menu-search-container/menu-search-container"; @@ -14,13 +15,10 @@ function Search(archive: Readonly) { // Content const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageNumberOfResults = language.text("resultaten", "results"); - const MessageNoResult = language.text("Geen resultaat", "No result"); - const MessageTryOtherQuery = language.text( - "Probeer een andere zoekopdracht", - "Try another search query" - ); - const MessagePageNumberToken = language.text("Pagina {pageNumber} van ", "Page {pageNumber} of "); // space at end + const MessageNumberOfResults = language.key(localization.MessageNumberOfResults); + const MessageNoResult = language.key(localization.MessageNoResult); + const MessageTryOtherQuery = language.key(localization.MessageTryOtherQuery); + const MessagePageNumberToken = language.key(localization.MessagePageNumberToken); // space at end const history = useLocation(); diff --git a/starsky/starsky/clientapp/src/containers/trash.tsx b/starsky/starsky/clientapp/src/containers/trash.tsx index 332d07e878..9b9eee0e9c 100644 --- a/starsky/starsky/clientapp/src/containers/trash.tsx +++ b/starsky/starsky/clientapp/src/containers/trash.tsx @@ -7,6 +7,7 @@ import { ArchiveContext, defaultStateFallback } from "../contexts/archive-contex import useGlobalSettings from "../hooks/use-global-settings"; import useLocation from "../hooks/use-location/use-location"; import { IArchiveProps } from "../interfaces/IArchiveProps"; +import localization from "../localization/localization.json"; import { Language } from "../shared/language"; import { URLPath } from "../shared/url-path"; @@ -14,12 +15,10 @@ function Trash(archive: Readonly) { // Content const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageEmptyTrash = language.text( - "Er staat niets in de prullenmand", - "There is nothing in the trash" - ); - const MessageNumberOfResults = language.text("resultaten", "results"); - const MessageNoResult = language.text("Geen resultaat", "No result"); + + const MessageNumberOfResults = language.key(localization.MessageNumberOfResults); + const MessageNoResult = language.key(localization.MessageNoResult); + const MessageEmptyTrash = language.key(localization.MessageEmptyTrash); const history = useLocation(); diff --git a/starsky/starsky/clientapp/src/interfaces/ILanguageLocalization.ts b/starsky/starsky/clientapp/src/interfaces/ILanguageLocalization.ts new file mode 100644 index 0000000000..aa1c1418c1 --- /dev/null +++ b/starsky/starsky/clientapp/src/interfaces/ILanguageLocalization.ts @@ -0,0 +1,11 @@ +export interface ILanguageLocalization { + nl: string; + en: string; + de: string; +} + +export const LanguageLocalizationExample: ILanguageLocalization = { + nl: "Nederlands", + en: "English", + de: "Deutsch" +}; diff --git a/starsky/starsky/clientapp/src/localization/localization.json b/starsky/starsky/clientapp/src/localization/localization.json index 9e33b83c4f..58155973a0 100644 --- a/starsky/starsky/clientapp/src/localization/localization.json +++ b/starsky/starsky/clientapp/src/localization/localization.json @@ -189,6 +189,11 @@ "nl": "Alles selecteren", "de": "Alles auswΓ€hlen" }, + "MessageAllName": { + "en": "All", + "nl": "Alles", + "de": "Alles" + }, "MessageSelectFurther": { "en": "Select further", "nl": "Verder selecteren", @@ -204,11 +209,6 @@ "nl": "Map maken", "de": "Ordner erstellen" }, - "MessageDisplayOptions": { - "en": "Display options", - "nl": "Weergave opties", - "de": "Anzeigeoptionen" - }, "MessageRenameDir": { "en": "Rename", "nl": "Naam wijzigen", @@ -244,6 +244,11 @@ "nl": "Deze video is niet gevonden", "de": "Dieses Video wurde nicht gefunden" }, + "MessageColorClassIsUpdated": { + "en": "Colorclass is updated", + "nl": "Colorclass is bijgewerkt", + "de": "Farbklasse wurde aktualisiert" + }, "ColorClassColour0": { "en": "Colorless", "nl": "Kleurloos", @@ -489,9 +494,509 @@ "nl": "Controleer je email adres", "de": "ÜberprΓΌfen Sie Ihre E-Mail-Adresse" }, + "MessagePasswordToShort": { + "en": "Use at least 8 characters for your password", + "nl": "Gebruik minimaal 8 tekens voor je wachtwoord", + "de": "Verwenden Sie mindestens 8 Zeichen fΓΌr Ihr Passwort" + }, + "MessageRejectedBadRequest": { + "en": "This request was rejected because the security requirements were not met (Error 400)", + "nl": "Dit verzoek is afgewezen aangezien er niet voldaan is aan de beveiligingseisen (Error 400)", + "de": "Dieses Anfrage wurde abgelehnt, weil die Sicherheitsanforderungen nicht erfΓΌllt wurden (Fehler 400)" + }, + "MessageRegistrationTurnedOff": { + "en": "Registration is turned off", + "nl": "Registratie is uitgezet", + "de": "Die Registrierung ist deaktiviert" + }, + "MessageSignInInstead": { + "en": "Sign in instead", + "nl": "In plaats daarvan inloggen", + "de": "Stattdessen anmelden" + }, + "MessageLegalCreateAccountHtml": { + "en": "By creating an account you agree to Starsky's Conditions of Use. Please see our Privacy Notice and our Cookies Notice", + "nl": "Door het creΓ«ren van een account gaat u akkoord met de Algemene Voorwaarden van Starsky. Raadpleeg en bekijk hier onze Privacykennisgeving en onze Cookieverklaring.", + "de": "Durch das Erstellen eines Kontos stimmen Sie den Allgemeinen GeschΓ€ftsbedingungen von Starsky zu. Bitte beachten Sie unsere DatenschutzerklΓ€rung und unsere Cookie-Richtlinie." + }, + "MessageFieldMaxLength": { + "en": "The field below can have a maximum of {maxlength} characters", + "nl": "Het onderstaande veld mag maximaal {maxlength} tekens hebben", + "de": "Das Feld unten kann maximal {maxlength} Zeichen enthalten" + }, + "MessageWrongUsernamePassword": { + "en": "Your username or password is incorrect. Try again", + "nl": "Je gebruikersnaam of wachtwoord is niet juist. Probeer het opnieuw", + "de": "Ihr Benutzername oder Ihr Passwort ist falsch. Bitte versuchen Sie es erneut." + }, + "MessageLockedOut": { + "en": "You've tried to login too many times, please try again in an hour", + "nl": "Je hebt te vaak geprobeerd in te loggen, probeer het over een uur nog een keer", + "de": "Sie haben zu oft versucht, sich anzumelden. Bitte versuchen Sie es in einer Stunde erneut." + }, + "MessageConnection": { + "en": "No connection is possible, please try again later", + "nl": "Er is geen verbinding mogelijk, probeer het later opnieuw", + "de": "Es ist keine Verbindung mΓΆglich, bitte versuchen Sie es spΓ€ter erneut." + }, + "MessageDatabaseConnection": { + "en": "There are database connection issues. Check and edit the appsettings", + "nl": "Er zijn problemen met de verbinding met de database. Controleer en pas de appsettings aan", + "de": "Es gibt Probleme mit der Verbindung zur Datenbank. ÜberprΓΌfen und bearbeiten Sie die App-Einstellungen." + }, + "LogoutWarning": { + "en": "Do you want to log out?", + "nl": "Wil je uitloggen?", + "de": "MΓΆchten Sie sich abmelden?" + }, + "MessageStayLoggedIn": { + "en": "Stay logged in", + "nl": "Blijf ingelogd", + "de": "Angemeldet bleiben" + }, + "MessageLogin": { + "en": "Login", + "nl": "Inloggen", + "de": "Anmelden" + }, + "MessageCreateAccount": { + "en": "Create account", + "nl": "Account maken", + "de": "Konto erstellen" + }, + "MessageMore": { + "en": "More", + "nl": "Meer", + "de": "Mehr" + }, + "MessagePrevious": { + "en": "Previous", + "nl": "Vorige", + "de": "Vorherige" + }, + "MessageNext": { + "en": "Next", + "nl": "Volgende", + "de": "NΓ€chste" + }, + "MessageAddName": { + "en": "HinzufΓΌgen zu", + "nl": "Toevoegen", + "de": "Mehr" + }, + "MessageOverwriteName": { + "en": "Overwrite", + "nl": "Overschrijven", + "de": "Überschreiben" + }, + "MessageTitleName": { + "en": "Title", + "nl": "Titel", + "de": "Titel" + }, + "MessageInfoName": { + "en": "Info", + "nl": "Info", + "de": "Info" + }, + "MessageWriteErrorReadOnly": { + "en": "One or more files are read only. Only the files with write permissions have been updated.", + "nl": "EΓ©n of meerdere bestanden zijn alleen lezen. Alleen de bestanden met schrijfrechten zijn geupdate.", + "de": "Eine oder mehrere Dateien sind schreibgeschΓΌtzt. Es wurden nur die Dateien mit Schreibrechten aktualisiert." + }, + "MessageErrorNotFoundSourceMissingRunSync": { + "en": "One or more files are already gone. Only the files that are present are updated. Run a manual sync", + "nl": "EΓ©n of meerdere bestanden zijn al verdwenen. Alleen de bestanden die wel aanwezig zijn geupdate. Draai een handmatige sync", + "de": "Eine oder mehrere Dateien sind bereits verschwunden. Es werden nur die vorhandenen Dateien aktualisiert. FΓΌhren Sie eine manuelle Synchronisierung durch" + }, + "MessageSearchAndReplaceNameLong": { + "en": "Search and replace", + "nl": "Zoeken en vervangen", + "de": "Suchen und ersetzen" + }, + "MessageSearchAndReplaceNameShort": { + "en": "Replace", + "nl": "Vervangen", + "de": "Ersetzen" + }, + "MessageModifyName": { + "en": "Modify", + "nl": "Wijzigen", + "de": "Γ„ndern" + }, + "MessageForceSyncCurrentFolder": { + "en": "Synchronize current directory manually", + "nl": "Handmatig synchroniseren van huidige map", + "de": "Aktuelles Verzeichnis manuell synchronisieren" + }, + "MessageWhereToFindReleaseReleasesUrlTokenHtml": { + "en": " {releasesToken}", + "nl": " {releasesToken}", + "de": " {releasesToken}" + }, + "MessageWhereToFindReleaseReleasesUrlTokenContent": { + "en": "Go to the release overview", + "nl": "Ga naar het release overzicht", + "de": "Gehen Sie zur Release-Übersicht" + }, + "WhereToFindReleaseElectronApp": { + "en": "Go to the Help menu and then release overview", + "nl": "Ga naar het Help menu en dan release overzicht", + "de": "Gehen Sie zum HilfemenΓΌ und dann zur Release-Übersicht" + }, + "MessageNewVersionUpdateToken": { + "en": "A new version is available {WhereToFindRelease}", + "nl": "Er is een nieuwe versie beschikbaar {WhereToFindRelease}", + "de": "Eine neue Version ist verfΓΌgbar {WhereToFindRelease}" + }, + "MessageHealthStatusCriticalErrorsWithTheFollowingComponents": { + "en": "There are critical errors in the following components:", + "nl": "Er zijn kritieke fouten in de volgende onderdelen:", + "de": "In den folgenden Komponenten liegen kritische Fehler vor:" + }, + "MessageNoPhotos": { + "en": "There are no pictures", + "nl": "Er zijn geen foto's", + "de": "Es gibt keine Bilder" + }, + "MessageFilesAdded": { + "en": "These files have been added", + "nl": "Deze bestanden zijn toegevoegd", + "de": "Diese Dateien wurden hinzugefΓΌgt" + }, + "MessageApplicationException": { + "en": "We have a disruption on the application right now", + "nl": "We hebben een op dit moment een verstoring op de applicatie", + "de": "Wir haben derzeit eine StΓΆrung bei der Anwendung" + }, + "MessageRefreshPageTryAgain": { + "en": "Please reload the application to try again", + "nl": "Herlaad de applicatie om het opnieuw te proberen", + "de": "Laden Sie die Anwendung herunter, um die Option zu testen" + }, + "MessagePublishSelection": { + "en": "Publish selection", + "nl": "Publiceer selectie", + "de": "Auswahl verΓΆffentlichen" + }, + "MessageGenericExportFail": { + "en": "Something went wrong with exporting", + "nl": "Er is iets misgegaan met exporteren", + "de": "Beim Exportieren ist ein Fehler aufgetreten" + }, + "MessageRetryExportFail": { + "en": "Retry this", + "nl": "Probeer het opnieuw", + "de": "Dies erneut versuchen" + }, + "MessageExportReady": { + "en": "The file {createZipKey} has finished exporting.", + "nl": "Het bestand {createZipKey} is klaar met exporteren.", + "de": "Die Datei {createZipKey} wurde erfolgreich exportiert." + }, + "MessageDownloadAsZipArchive": { + "en": "Download as a zip archive", + "nl": "Download als zip-archief", + "de": "Als ZIP-Archiv herunterladen" + }, + "MessageOneMomentPlease": { + "en": "One moment please", + "nl": "Een moment geduld alstublieft", + "de": "Einen Moment bitte" + }, + "MessageItemName": { + "en": "What is the item about?", + "nl": "Waar gaat het item over?", + "de": "Worum geht es bei dem Element?" + }, + "MessageItemNameInUse": { + "en": "This name is already in use, please choose another name", + "nl": "Deze naam is al in gebruik, kies een andere naam", + "de": "Dieser Name wird bereits verwendet. Bitte wΓ€hlen Sie einen anderen Namen" + }, + "MessagePublishProfileName": { + "en": "Profile setting", + "nl": "Profiel instelling", + "de": "Profil-Einstellung" + }, + "MessagePublishProfileNamesErrored": { + "en": "Profile setting: {publishProfileNames} contains filepath errors", + "nl": "Profiel instelling: {publishProfileNames} bevat bestand locatie fouten", + "de": "Profil-Einstellung: {publishProfileNames} enthΓ€lt Dateipfadfehler" + }, + "MessageSelectionName": { + "en": "Selection", + "nl": "Selectie", + "de": "Auswahl" + }, + "MessageReadOnlyFolder": { + "en": "Read only folder", + "nl": "Alleen lezen map", + "de": "Nur-Lese-Ordner" + }, + "MessageUpdateLabels": { + "en": "Update labels", + "nl": "Labels wijzigingen", + "de": "Etiketten aktualisieren" + }, + "MessageColorClassification": { + "en": "Color Classification", + "nl": "Kleur-Classificatie", + "de": "Farbklassifizierung" + }, + "MessageDateTimeAgoEdited": { + "en": "ago edited", + "nl": "geleden bewerkt", + "de": "vorher bearbeitet" + }, + "MessageDateLessThan1Minute": { + "en": "less than one minute", + "nl": "minder dan één minuut", + "de": "weniger als eine Minute" + }, + "MessageDateMinutes": { + "en": "minutes", + "nl": "minuten", + "de": "Minuten" + }, + "MessageDateHour": { + "en": "hour", + "nl": "uur", + "de": "Stunde" + }, + "MessageCopiedLabels": { + "en": "The labels have been copied", + "nl": "De labels zijn gekopieerd", + "de": "Die Labels wurden kopiert" + }, + "MessagePasteLabels": { + "en": "The labels have been overwritten", + "nl": "De labels zijn overschreven", + "de": "Die Labels wurden ΓΌberschrieben" + }, + "MessageCreationDate": { + "en": "Creation date", + "nl": "Aanmaakdatum", + "de": "Erstellungsdatum" + }, + "MessageCreationDateIsAtUnknownTime": { + "en": "is at an unknown time", + "nl": "is op een onbekend moment", + "de": "ist zu einem unbekannten Zeitpunkt" + }, + "MessageNounNameless": { + "en": "Unnamed", + "nl": "Naamloze", + "de": "Unbenannt" + }, + "MessageNounNone": { + "en": "Not any", + "nl": "Geen enkele", + "de": "Nicht eine" + }, + "MessageLocation": { + "en": "location", + "nl": "locatie", + "de": "Lokalisierung" + }, + "MessageCreateNewFolderFeature": { + "en": "Create new folder", + "nl": "Nieuwe map aanmaken", + "de": "Neuen Ordner erstellen" + }, + "MessageNonValidDirectoryName": { + "en": "Check the name, this folder cannot be created in this way", + "nl": "Controleer de naam, deze map kan niet zo worden aangemaakt", + "de": "ÜberprΓΌfen Sie den Namen, dieser Ordner kann nicht auf diese Weise erstellt werden" + }, + "MessageGeneralMkdirCreateError": { + "en": "An error occurred while creating this folder", + "nl": "Er is misgegaan met het aanmaken van deze map", + "de": "Beim Erstellen dieses Ordners ist ein Fehler aufgetreten" + }, + "MessageDirectoryExistError": { + "en": "The folder already exists, try a different name", + "nl": "De map bestaat al, probeer een andere naam", + "de": "Der Ordner existiert bereits. Versuchen Sie einen anderen Namen" + }, + "MessageRenameCurrentFolder": { + "en": "Rename current folder", + "nl": "Huidige mapnaam wijzigen", + "de": "Aktuellen Ordner umbenennen" + }, + "MessageRemoveCache": { + "en": "Refresh cache of current directory", + "nl": "Verwijder cache van huidige map", + "de": "Aktualisieren Sie den Cache des aktuellen Verzeichnisses" + }, + "MessageGeoSync": { + "en": "Automatically add geolocation", + "nl": "Voeg geolocatie automatisch toe", + "de": "Geolocation automatisch hinzufΓΌgen" + }, + "MessageGeoSyncExplainer": { + "en": "The location is derived from a gpx file located in the current folder and based on the location place names are appended to the images", + "nl": "De locatie wordt afgeleid van een gpx bestand die zich in de huidige map bevind en op basis van de locatie worden er plaatsnamen bij de afbeeldingen gevoegd", + "de": "Der Ort wird aus einer in dem aktuellen Ordner befindlichen gpx-Datei abgeleitet und basierend auf dem Ort werden Ortsnamen den Bildern hinzugefΓΌgt" + }, + "MessageManualThumbnailSync": { + "en": "Generate thumbnail images", + "nl": "Thumbnail afbeeldingen generen", + "de": "Miniaturbilder generieren" + }, + "MessageManualThumbnailSyncExplainer": { + "en": "This action generate on the background lots of thumbnail images, this does impact the performance", + "nl": "Deze actie genereert op de achtergrond veel miniatuurafbeeldingen, dit heeft invloed op de prestaties", + "de": "Diese Aktion generiert im Hintergrund viele Miniaturbilder, was sich auf die Leistung auswirkt" + }, + "MessageNonValidExtension": { + "en": "This file cannot be saved", + "nl": "Dit bestand kan zo niet worden weggeschreven", + "de": "Diese Datei kann nicht gespeichert werden" + }, + "MessageChangeToDifferentExtension": { + "en": "Pay attention! You change the file extension, which can make it unreadable", + "nl": "Let op! Je veranderd de extensie van het bestand, deze kan hierdoor onleesbaar worden", + "de": "Achtung! Sie Γ€ndern die Dateierweiterung, was dazu fΓΌhren kann, dass sie nicht lesbar wird" + }, + "MessageRenameServerError": { + "en": "Something went wrong with the request, please try again later", + "nl": "Er is iets misgegaan met de aanvraag, probeer het later opnieuw", + "de": "Bei der Anfrage ist ein Fehler aufgetreten. Bitte versuchen Sie es spΓ€ter erneut" + }, + "MessageDisplayOptions": { + "en": "Display options", + "nl": "Weergave opties", + "de": "Anzeigeoptionen" + }, + "MessageDisplayOptionsSwitchButtonCollectionsOff": { + "en": "Show raw files", + "nl": "Toon raw bestanden", + "de": "Rohdateien anzeigen" + }, + "MessageDisplayOptionsSwitchButtonCollectionsOn": { + "en": "Hide Raw files", + "nl": "Verberg raw bestanden", + "de": "Rohdateien ausblenden" + }, + "MessageDisplayOptionsSwitchButtonIsSingleItemOff": { + "en": "Load everything", + "nl": "Alles inladen", + "de": "Alles laden" + }, + "MessageDisplayOptionsSwitchButtonIsSingleItemOn": { + "en": "Small loading", + "nl": "Klein inladen", + "de": "Kleine Ladung" + }, + "MessageDisplayOptionsSwitchButtonIsSocketOn": { + "en": "Realtime updates", + "nl": "Realtime updates", + "de": "Echtzeitupdates" + }, + "MessageDisplayOptionsSwitchButtonIsSocketOff": { + "en": "Refresh yourself", + "nl": "Ververs zelf", + "de": "Selbst aktualisieren" + }, + "MessageDownloadSelection": { + "en": "Download selection", + "nl": "Download selectie", + "de": "Auswahl herunterladen" + }, + "MessageOriginalFile": { + "en": "Original file", + "nl": "Origineel bestand", + "de": "Originaldatei" + }, + "MessageThumbnailFile": { + "en": "Thumbnail", + "nl": "Thumbnail", + "de": "Vorschaubild" + }, + "MessageModalDatetime": { + "en": "Edit date and time", + "nl": "Datum en tijd bewerken", + "de": "Datum und Uhrzeit bearbeiten" + }, + "MessageYear": { + "en": "Year", + "nl": "Jaar", + "de": "Jahr" + }, + "MessageMonth": { + "en": "Month", + "nl": "Maand", + "de": "Monat" + }, + "MessageDate": { + "en": "Day", + "nl": "Dag", + "de": "Tag" + }, + "MessageTime": { + "en": "Time", + "nl": "Tijd", + "de": "Zeit" + }, + "MessageErrorDatetime": { + "en": "The date and time were entered incorrectly", + "nl": "De datum en tijd zijn incorrect ingegeven", + "de": "Das Datum und die Uhrzeit wurden falsch eingegeben" + }, + "MessageDeleteIntroText": { + "en": "Are you sure you want to delete this file from all devices?", + "nl": "Weet je zeker dat je dit bestand wilt verwijderen van alle devices?", + "de": "MΓΆchten Sie diese Datei wirklich von allen GerΓ€ten lΓΆschen?" + }, + "MessageConnectionRealtimeError": { + "en": "The connection is not quite right. We are trying to fix it", + "nl": "De verbinding is niet helemaal okΓ©. We proberen het te herstellen", + "de": "Die Verbindung stimmt nicht ganz. Wir versuchen es zu beheben" + }, + "MessageApplicationFailed": { + "en": "The application has failed. Please reload it to try it again", + "nl": "De verbinding is niet helemaal okΓ©. Probeer te herladen", + "de": "Die Verbindung stimmt nicht ganz. Wir versuchen es zu beheben" + }, + "MessageNumberOfResults": { + "en": "results", + "nl": "resultaten", + "de": "Ergebnisse" + }, + "MessageNoResult": { + "en": "No result", + "nl": "Geen resultaat", + "de": "Kein Ergebnis" + }, + "MessageTryOtherQuery": { + "en": "Try another search query", + "nl": "Probeer een andere zoekopdracht", + "de": "Versuchen Sie eine andere Suchanfrage" + }, + "MessagePageNumberToken": { + "en": "Page {pageNumber} of ", + "nl": "Pagina {pageNumber} van ", + "de": "Seite {pageNumber} von " + }, + "MessageEmptyTrash": { + "en": "There is nothing in the trash", + "nl": "Er staat niets in de prullenmand", + "de": "Es befindet sich nichts im Papierkorb" + }, + "MessageNotFound": { + "en": "Not Found", + "nl": "Oeps niet gevonden", + "de": "Nicht gefunden" + }, + "MessageGoToHome": { + "en": "Go to the homepage", + "nl": "Ga naar de homepagina", + "de": "Gehe zur Startseite" + }, "temp1": { - "en": "", - "nl": "", - "de": "" + "en": "More", + "nl": "Meer", + "de": "Mehr" } } diff --git a/starsky/starsky/clientapp/src/pages/not-found-page.tsx b/starsky/starsky/clientapp/src/pages/not-found-page.tsx index cb1acab0f4..575e5b0b45 100644 --- a/starsky/starsky/clientapp/src/pages/not-found-page.tsx +++ b/starsky/starsky/clientapp/src/pages/not-found-page.tsx @@ -2,6 +2,7 @@ import { FunctionComponent } from "react"; import Link from "../components/atoms/link/link"; import MenuDefault from "../components/organisms/menu-default/menu-default"; import useGlobalSettings from "../hooks/use-global-settings"; +import localization from "../localization/localization.json"; import { Language } from "../shared/language"; import { UrlQuery } from "../shared/url-query"; @@ -10,8 +11,8 @@ export const NotFoundPage: FunctionComponent = () => { const settings = useGlobalSettings(); const language = new Language(settings.language); - const MessageNotFound = language.text("Oeps niet gevonden", "Not Found"); - const MessageGoToHome = language.text("Ga naar de homepagina", "Go to the homepage"); + const MessageNotFound = language.key(localization.MessageNotFound); + const MessageGoToHome = language.key(localization.MessageGoToHome); return (
diff --git a/starsky/starsky/clientapp/src/shared/language.spec.ts b/starsky/starsky/clientapp/src/shared/language.spec.ts index 4e2870037a..baeafb7b7b 100644 --- a/starsky/starsky/clientapp/src/shared/language.spec.ts +++ b/starsky/starsky/clientapp/src/shared/language.spec.ts @@ -5,11 +5,11 @@ describe("keyboard", () => { describe("text", () => { it("get different content (dutch)", () => { - const result = language.text("dutch", "english"); + const result = language.text("dutch", "english", "deutsch"); expect(result).toBe("dutch"); }); it("get different content (english)", () => { - const result = new Language(SupportedLanguages.en).text("dutch", "english"); + const result = new Language(SupportedLanguages.en).text("dutch", "english", "deutsch"); expect(result).toBe("english"); }); }); @@ -18,17 +18,51 @@ describe("keyboard", () => { it("get different content (dutch)", () => { const result = language.key({ nl: "dutch", - en: "english" + en: "english", + de: "deutsch" }); expect(result).toBe("dutch"); }); it("get different content (english)", () => { const result = new Language(SupportedLanguages.en).key({ nl: "dutch", - en: "english" + en: "english", + de: "deutsch" }); expect(result).toBe("english"); }); + + it("replace keys - english", () => { + const data = { + nl: "Het onderstaande veld mag maximaal {maxlength} tekens hebben", + en: "The field below can have a maximum of {maxlength} characters", + de: "Das Feld unten kann maximal {maxlength} Zeichen enthalten" + }; + const maxlength = 14; + + const result = new Language(SupportedLanguages.en).key( + data, + ["{maxlength}"], + [maxlength.toString()] + ); + expect(result).toBe("The field below can have a maximum of 14 characters"); + }); + + it("replace keys - german", () => { + const data = { + nl: "Het onderstaande veld mag maximaal {maxlength} tekens hebben", + en: "The field below can have a maximum of {maxlength} characters", + de: "Das Feld unten kann maximal {maxlength} Zeichen enthalten" + }; + const maxlength = 14; + + const result = new Language(SupportedLanguages.de).key( + data, + ["{maxlength}"], + [maxlength.toString()] + ); + expect(result).toBe("Das Feld unten kann maximal 14 Zeichen enthalten"); + }); }); describe("token", () => { diff --git a/starsky/starsky/clientapp/src/shared/language.ts b/starsky/starsky/clientapp/src/shared/language.ts index ccbbb54b07..d1dc2f6d0e 100644 --- a/starsky/starsky/clientapp/src/shared/language.ts +++ b/starsky/starsky/clientapp/src/shared/language.ts @@ -1,6 +1,9 @@ +import { ILanguageLocalization } from "../interfaces/ILanguageLocalization"; + export enum SupportedLanguages { nl = "nl", - en = "en" + en = "en", + de = "de" } export class Language { @@ -11,25 +14,30 @@ export class Language { this.selectedLanguage = selectedLanguage; } - private selectedLanguage: SupportedLanguages; + private readonly selectedLanguage: SupportedLanguages; /** * WIP - * @param key * @returns + * @param content */ - public key(content: { en: string; nl: string }): string { - return this.text(content.nl, content.en); + public key(content: ILanguageLocalization, token?: string[], dynamicValue?: string[]): string { + const text = this.text(content.nl, content.en, content.de); + if (!token || !dynamicValue) { + return text; + } + return this.token(text, token, dynamicValue); } /** * Get the right content based on the language * Map used to be Map and nl = "nl" as any */ - public text(nl: string, en: string): string { + public text(nl: string, en: string, de: string): string { const selectedLanguageMap = new Map([ [SupportedLanguages.nl, nl], - [SupportedLanguages.en, en] + [SupportedLanguages.en, en], + [SupportedLanguages.de, de] ]); const content = selectedLanguageMap.get(this.selectedLanguage); diff --git a/starsky/starsky/clientapp/vite.config.ts b/starsky/starsky/clientapp/vite.config.ts index 6233d54087..6f5e3cebf5 100644 --- a/starsky/starsky/clientapp/vite.config.ts +++ b/starsky/starsky/clientapp/vite.config.ts @@ -9,7 +9,7 @@ export default defineConfig({ outDir: "build", assetsDir: "assets", assetsInlineLimit: 0, - chunkSizeWarningLimit: 600 + chunkSizeWarningLimit: 650 }, optimizeDeps: { include: ["leaflet", "core-js", "react", "react-dom", "react-router-dom"] From 1ad7e7a4db30cb866e3278fdf3c1b3414a27843b Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 28 Feb 2024 19:20:40 +0100 Subject: [PATCH 122/125] enable german as language --- .../organisms/menu-archive/menu-archive.tsx | 3 +- .../menu-detail-view/menu-detail-view.tsx | 3 +- .../organisms/menu-search/menu-search.tsx | 3 +- .../src/hooks/use-global-settings.spec.ts | 65 ++++++++++++++++++- .../src/hooks/use-global-settings.ts | 16 +++++ .../src/localization/localization.json | 2 +- 6 files changed, 87 insertions(+), 5 deletions(-) diff --git a/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx b/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx index d7bca282d4..9d2f1ee5bb 100644 --- a/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/menu-archive/menu-archive.tsx @@ -40,6 +40,7 @@ const MenuArchive: React.FunctionComponent = memo(() => { // Content const MessageSelectAction = language.key(localization.MessageSelectAction); + const MessageLabels = language.key(localization.MessageLabels); const [hamburgerMenu, setHamburgerMenu] = React.useState(false); const [enableMoreMenu, setEnableMoreMenu] = React.useState(false); @@ -201,7 +202,7 @@ const MenuArchive: React.FunctionComponent = memo(() => { }} onClick={() => toggleLabels()} > - Labels + {MessageLabels} ) : null} diff --git a/starsky/starsky/clientapp/src/components/organisms/menu-detail-view/menu-detail-view.tsx b/starsky/starsky/clientapp/src/components/organisms/menu-detail-view/menu-detail-view.tsx index be0005d621..4141da9e09 100644 --- a/starsky/starsky/clientapp/src/components/organisms/menu-detail-view/menu-detail-view.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/menu-detail-view/menu-detail-view.tsx @@ -59,6 +59,7 @@ const MenuDetailView: React.FunctionComponent = ({ state, d const MessageMoveToTrash = language.key(localization.MessageMoveToTrash); const MessageIncludingColonWord = language.key(localization.MessageIncludingColonWord); const MessageRestoreFromTrash = language.key(localization.MessageRestoreFromTrash); + const MessageLabels = language.key(localization.MessageLabels); const history = useLocation(); @@ -294,7 +295,7 @@ const MenuDetailView: React.FunctionComponent = ({ state, d event.key === "Enter" && toggleLabels(); }} > - Labels + {MessageLabels} diff --git a/starsky/starsky/clientapp/src/components/organisms/menu-search/menu-search.tsx b/starsky/starsky/clientapp/src/components/organisms/menu-search/menu-search.tsx index df7de8ff9d..8e4007ac7a 100644 --- a/starsky/starsky/clientapp/src/components/organisms/menu-search/menu-search.tsx +++ b/starsky/starsky/clientapp/src/components/organisms/menu-search/menu-search.tsx @@ -38,6 +38,7 @@ const MenuSearch: React.FunctionComponent = ({ state, dispatch // Content const MessageSelectAction = language.key(localization.MessageSelectAction); + const MessageLabels = language.key(localization.MessageLabels); // Selection const history = useLocation(); @@ -131,7 +132,7 @@ const MenuSearch: React.FunctionComponent = ({ state, dispatch event.key === "Enter" && toggleLabels(); }} > - Labels + {MessageLabels} ) : null} diff --git a/starsky/starsky/clientapp/src/hooks/use-global-settings.spec.ts b/starsky/starsky/clientapp/src/hooks/use-global-settings.spec.ts index b8eb372fca..762f7c3040 100644 --- a/starsky/starsky/clientapp/src/hooks/use-global-settings.spec.ts +++ b/starsky/starsky/clientapp/src/hooks/use-global-settings.spec.ts @@ -1,6 +1,6 @@ import { SupportedLanguages } from "../shared/language"; -import useGlobalSettings, { IGlobalSettings } from "./use-global-settings"; import { mountReactHook } from "./___tests___/test-hook"; +import useGlobalSettings, { IGlobalSettings } from "./use-global-settings"; describe("useGlobalSettings", () => { describe("language", () => { @@ -43,5 +43,68 @@ describe("useGlobalSettings", () => { expect(hook.language).toBe(SupportedLanguages.nl); }); + + it("get german language de", () => { + const languageGetter = jest.spyOn(window.navigator, "language", "get"); + languageGetter.mockReturnValue("de"); + + runHook(); + + expect(hook.language).toBe(SupportedLanguages.de); + }); + + it("get german language de-AT", () => { + const languageGetter = jest.spyOn(window.navigator, "language", "get"); + languageGetter.mockReturnValue("de-AT"); + + runHook(); + + expect(hook.language).toBe(SupportedLanguages.de); + }); + + it("get german language de-BE", () => { + const languageGetter = jest.spyOn(window.navigator, "language", "get"); + languageGetter.mockReturnValue("de-BE"); + + runHook(); + + expect(hook.language).toBe(SupportedLanguages.de); + }); + + it("get german language de-CH", () => { + const languageGetter = jest.spyOn(window.navigator, "language", "get"); + languageGetter.mockReturnValue("de-CH"); + + runHook(); + + expect(hook.language).toBe(SupportedLanguages.de); + }); + + it("get german language de-IT", () => { + const languageGetter = jest.spyOn(window.navigator, "language", "get"); + languageGetter.mockReturnValue("de-IT"); + + runHook(); + + expect(hook.language).toBe(SupportedLanguages.de); + }); + + it("get german language de-LI", () => { + const languageGetter = jest.spyOn(window.navigator, "language", "get"); + languageGetter.mockReturnValue("de-LI"); + + runHook(); + + expect(hook.language).toBe(SupportedLanguages.de); + }); + + it("get german language de-LU", () => { + const languageGetter = jest.spyOn(window.navigator, "language", "get"); + languageGetter.mockReturnValue("de-LU"); + + runHook(); + + expect(hook.language).toBe(SupportedLanguages.de); + }); }); }); diff --git a/starsky/starsky/clientapp/src/hooks/use-global-settings.ts b/starsky/starsky/clientapp/src/hooks/use-global-settings.ts index 2135a9088d..38f09651cb 100644 --- a/starsky/starsky/clientapp/src/hooks/use-global-settings.ts +++ b/starsky/starsky/clientapp/src/hooks/use-global-settings.ts @@ -16,6 +16,22 @@ const useGlobalSettings = (): IGlobalSettings => { return SupportedLanguages.nl; case "nl": return SupportedLanguages.nl; + case "de": + return SupportedLanguages.de; + case "de-de": + return SupportedLanguages.de; + case "de-at": + return SupportedLanguages.de; + case "de-be": + return SupportedLanguages.de; + case "de-ch": + return SupportedLanguages.de; + case "de-it": + return SupportedLanguages.de; + case "de-li": + return SupportedLanguages.de; + case "de-lu": + return SupportedLanguages.de; default: return SupportedLanguages.en; } diff --git a/starsky/starsky/clientapp/src/localization/localization.json b/starsky/starsky/clientapp/src/localization/localization.json index 58155973a0..29a2187343 100644 --- a/starsky/starsky/clientapp/src/localization/localization.json +++ b/starsky/starsky/clientapp/src/localization/localization.json @@ -122,7 +122,7 @@ "MessageCloseDialogBackToFolder": { "en": "Parent folder", "nl": "Terug naar map", - "de": "Übergeordneter Ordner" + "de": "Elternordner" }, "MessageIncludingColonWord": { "en": "Including: ", From 1a5ce962e72e72fb4af596f9c60ab200429a8768 Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 28 Feb 2024 20:05:45 +0100 Subject: [PATCH 123/125] update docs --- ...ing-started-configuration-desktop-open.jpg | Bin 0 -> 72096 bytes documentation/docs/features/bulk-editing.md | 14 ++- documentation/docs/features/stacks.md | 14 ++- .../configuration/_category_.json | 8 ++ .../{ => configuration}/config-options.md | 20 +++-- .../configuration/desktop-open.md | 79 +++++++++++++++++ .../docs/getting-started/desktop/openwith.md | 32 ------- documentation/docs/getting-started/setup.md | 80 +++++++++++++----- starsky/starsky/readme.md | 5 +- 9 files changed, 184 insertions(+), 68 deletions(-) create mode 100644 documentation/docs/assets/getting-started-configuration-desktop-open.jpg create mode 100644 documentation/docs/getting-started/configuration/_category_.json rename documentation/docs/getting-started/{ => configuration}/config-options.md (65%) create mode 100644 documentation/docs/getting-started/configuration/desktop-open.md delete mode 100644 documentation/docs/getting-started/desktop/openwith.md diff --git a/documentation/docs/assets/getting-started-configuration-desktop-open.jpg b/documentation/docs/assets/getting-started-configuration-desktop-open.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b3508f6baff10e6c8164947e9d72dd73c7964c53 GIT binary patch literal 72096 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 lObXEf@<1~LKm!SrAcIJdp>Y3VB|s4mpl*X Note: If you enter an invalid ApplicationPath location: The application will open the file with +> the system default application + +```json +{ + "DefaultDesktopEditor": [ + { + "ApplicationPath": "/Applications/Adobe Photoshop 2020/Adobe Photoshop 2020.app", + "ImageFormats": ["jpg", "bmp", "png", "gif", "tiff"] + } + ] +} +``` + +### Collections / Stacks + +When opening an image from a collection/stack, the desktop application will one of both files +A collection is for example two files in one folder: `2021-01-01-IMG_1234.jpg` +and `2021-01-01-IMG_1234.dng` +The default display is to show the jpeg first. + +> Note: The default setting is to open the jpeg file first + +### Raw First + +So the raw file will be open if available + +```json +{ + "DesktopCollectionsOpen": "2" +} +``` + +### Jpeg first + +If the raw file is available, the jpeg file will be open first + +```json +{ + "DesktopCollectionsOpen": "1" +} +``` \ No newline at end of file diff --git a/documentation/docs/getting-started/desktop/openwith.md b/documentation/docs/getting-started/desktop/openwith.md deleted file mode 100644 index 7b3e3b43a3..0000000000 --- a/documentation/docs/getting-started/desktop/openwith.md +++ /dev/null @@ -1,32 +0,0 @@ -# Configure Open With - -Settings - - - -## DefaultDesktopEditor - -```json -{ - "DefaultDesktopEditor": [ - { - "ApplicationPath": "/Applications/Adobe Photoshop 2020/Adobe Photoshop 2020.app", - "ImageFormats": ["jpg", "bmp", "png", "gif", "tiff"] - } - ] -} -``` - -### Raw First - -```json -{ - "DesktopCollectionsOpen": "2" -} -``` -### Jpeg first -```json -{ - "DesktopCollectionsOpen": "1" -} -``` \ No newline at end of file diff --git a/documentation/docs/getting-started/setup.md b/documentation/docs/getting-started/setup.md index 3027ffd1e6..6c9cb4841f 100644 --- a/documentation/docs/getting-started/setup.md +++ b/documentation/docs/getting-started/setup.md @@ -1,67 +1,105 @@ --- -sidebar_position: 4 +sidebar_position: 5 --- # Setup server app -Starsky can be installed on all operating systems supporting Docker, as well as FreeBSD, Raspberry Pi, and many NAS devices. +Starsky can be installed on all operating systems supporting Docker, as well as FreeBSD, Raspberry +Pi, and many NAS devices. There are multiple ways of installing Starsky: 1. **As background service (systemd or pm2 service)**
Run it as system service. All dependencies are included in the application - There are multiple options to run it as a service, see [systemd](linux-systemd.md), [macOS launchctl](macos-launchctl.md), [windows service](windows-as-server/windows-service.md) or [pm2](pm2.md) for more information + There are multiple options to run it as a service, + see [systemd](linux-systemd.md), [macOS launchctl](macos-launchctl.md), [windows service](windows-as-server/windows-service.md) + or [pm2](pm2.md) for more information 2. **Docker**
- When using Docker we recommend running Starsky with Docker Compose when hosting it on a private server. It is available for Mac, Linux, and Windows. [Read more about docker configuration here](docker/docker-compose.md) + When using Docker we recommend running Starsky with Docker Compose when hosting it on a private + server. It is available for Mac, Linux, and + Windows. [Read more about docker configuration here](docker/docker-compose.md) 3. **In IIS** (Windows Pro and Server Only)
- When running the Pro and Server version of windows the IIS webserver can be used [Read more about IIS configuration here](windows-as-server/iis.md) + When running the Pro and Server version of windows the IIS webserver can be + used [Read more about IIS configuration here](windows-as-server/iis.md) -Once the initial setup is complete, our [First Steps πŸ‘£ ](first-steps) tutorial guides you through the user interface and settings to ensure your library is indexed according to your individual preferences. +Once the initial setup is complete, our [First Steps πŸ‘£ ](first-steps) tutorial guides you through +the user interface and settings to ensure your library is indexed according to your individual +preferences. -> > Our stable version and development preview have been built into a single multi-arch Docker image for 64-bit AMD, Intel, and ARM processors. That means, Raspberry Pi 3 / 4, Apple Silicon, and other ARM64-based devices can pull from the same repository, enjoy the exact same functionality, and can follow the regular installation instructions after going through a short list of requirements. See FAQs for instructions and notes on alternative installation methods. +> > Our stable version and development preview have been built into a single multi-arch Docker image +> > for 64-bit AMD, Intel, and ARM processors. That means, Raspberry Pi 3 / 4, Apple Silicon, and other +> > ARM64-based devices can pull from the same repository, enjoy the exact same functionality, and can +> > follow the regular installation instructions after going through a short list of requirements. See +> > FAQs for instructions and notes on alternative installation methods. ## Roadmap -Our vision is to provide the most user- and privacy-friendly solution to keep your pictures organized and accessible. The roadmap shows what tasks are in progress, what needs testing, and which features are going to be implemented next. +Our vision is to provide the most user- and privacy-friendly solution to keep your pictures +organized and accessible. The roadmap shows what tasks are in progress, what needs testing, and +which features are going to be implemented next. -We have a low bug policy and do our best to help users when they need support or have other questions. This comes at a price, as we can't give exact deadlines for new features. +We have a low bug policy and do our best to help users when they need support or have other +questions. This comes at a price, as we can't give exact deadlines for new features. -Having said that, funding really has the highest impact. [So users can do their part and become a sponsor to get their favorite features as soon as possible.](https://www.paypal.me/qdrawmedia) +Having said that, funding really has the highest +impact. [So users can do their part and become a sponsor to get their favorite features as soon as possible.](https://www.paypal.me/qdrawmedia) ## System Requirements -You should host Starsky on a server with at least 2 cores, 3 GB of physical memory, 1 and a 64-bit operating system. Beyond these minimum requirements, the amount of RAM should match the number of CPU cores. Indexing large photo and video collections also benefits greatly from local SSD storage, especially for the database and cache files. +You should host Starsky on a server with at least 2 cores, 3 GB of physical memory, 1 and a 64-bit +operating system. Beyond these minimum requirements, the amount of RAM should match the number of +CPU cores. Indexing large photo and video collections also benefits greatly from local SSD storage, +especially for the database and cache files. -If your server has less than 4 GB of swap space or a manual memory/swap limit is set, this can cause unexpected restarts, for example, when the indexer temporarily needs more memory to process large files. High-resolution panoramic images may require additional swap space and/or physical memory above the recommended minimum. +If your server has less than 4 GB of swap space or a manual memory/swap limit is set, this can cause +unexpected restarts, for example, when the indexer temporarily needs more memory to process large +files. High-resolution panoramic images may require additional swap space and/or physical memory +above the recommended minimum. -> We take no responsibility for instability or performance problems if your device does not meet the requirements. +> We take no responsibility for instability or performance problems if your device does not meet the +> requirements. ### Databases -Starsky is compatible with SQLite 3 and MariaDB 10.5.12+.2 Note that SQLite is generally not a good choice for users who require scalability and high performance, and that support for MySQL 8 has been discontinued due to low demand and missing features. +Starsky is compatible with SQLite 3 and MariaDB 10.5.12+.2 Note that SQLite is generally not a good +choice for users who require scalability and high performance, and that support for MySQL 8 has been +discontinued due to low demand and missing features. ### Browsers -Built as a Progressive Web App (PWA), the web interface works with most modern browsers, and runs best on Chrome, Chromium, Safari, Firefox, and Edge. You can conveniently install it on the home screen of all major operating systems and mobile devices. Internet Explorer is not supported. +Built as a Progressive Web App (PWA), the web interface works with most modern browsers, and runs +best on Chrome, Chromium, Safari, Firefox, and Edge. You can conveniently install it on the home +screen of all major operating systems and mobile devices. Internet Explorer is not supported. ### Video playback -Not all video and audio formats can be played with every browser. For example, AAC - the default audio codec for MPEG-4 AVC / H.264 - is supported natively in Chrome, Safari, and Edge, while it is only optionally supported by the OS in Firefox and Opera. +Not all video and audio formats can be played with every browser. For example, AAC - the default +audio codec for MPEG-4 AVC / H.264 - is supported natively in Chrome, Safari, and Edge, while it is +only optionally supported by the OS in Firefox and Opera. ### HTTPS -If you install Starsky on a public server outside your home network, always run it behind a secure HTTPS reverse proxy such as Traefik or Caddy. Your files and passwords will otherwise be transmitted in clear text and can be intercepted by anyone, including your provider, hackers, and governments. +If you install Starsky on a public server outside your home network, always run it behind a secure +HTTPS reverse proxy such as Traefik or Caddy. Your files and passwords will otherwise be transmitted +in clear text and can be intercepted by anyone, including your provider, hackers, and governments. ## Getting Support -If you need help installing our software at home, you post your question in GitHub Discussions. Common problems can be quickly diagnosed and solved using our Troubleshooting Checklists. +If you need help installing our software at home, you post your question in GitHub Discussions. +Common problems can be quickly diagnosed and solved using our Troubleshooting Checklists. ### Sponsor us -We'll do our best to answer all your questions. In return, we ask you can sponsor us. Think of "free software" as in "free speech," not as in "free beer". Thank you! +We'll do our best to answer all your questions. In return, we ask you can sponsor us. Think of "free +software" as in "free speech," not as in "free beer". Thank you! -In exchange for their continued support, sponsors are also welcome to request direct technical support via email. Please bear with us if we are unable to get back to you immediately due to the high volume of emails and contact requests we receive. +In exchange for their continued support, sponsors are also welcome to request direct technical +support via email. Please bear with us if we are unable to get back to you immediately due to the +high volume of emails and contact requests we receive. -> > We kindly ask you not to report bugs via GitHub Issues unless you are certain to have found a fully reproducible and previously unreported issue that must be fixed directly in the app. Contact us or a community member if you need help, it could be a local configuration problem, or a misunderstanding in how the software works. +> > We kindly ask you not to report bugs via GitHub Issues unless you are certain to have found a +> > fully reproducible and previously unreported issue that must be fixed directly in the app. Contact +> > us or a community member if you need help, it could be a local configuration problem, or a +> > misunderstanding in how the software works. diff --git a/starsky/starsky/readme.md b/starsky/starsky/readme.md index ebc18f21b6..ef2cd3f11a 100644 --- a/starsky/starsky/readme.md +++ b/starsky/starsky/readme.md @@ -55,9 +55,10 @@ machinename with your computer name in lowercase)_ ### Required settings to start -1. To start it is __not__ mandatory to adjust any settings. +1. To start it is __not__ mandatory to adjust any settings. However it is recommended to change the + `StorageFolder` to a folder on your local machine where the picture should be located. -### Recommend settings +### Recommend settings {#recommend-settings} 1. `ThumbnailTempFolder` - For storing thumbnails ( default: `./bin/Debug/net8.0/thumbnailTempFolder`) From 80e946a6e752c43ce39d44d763179389a088fcfb Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 28 Feb 2024 20:28:34 +0100 Subject: [PATCH 124/125] keep a changelog --- history.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/history.md b/history.md index e13f965622..87205a2c02 100644 --- a/history.md +++ b/history.md @@ -43,7 +43,23 @@ Semantic Versioning 2.0.0 is from version 0.1.6+ ## version 0.6.0-beta.2 - _(Unreleased)_ - 2024-02-? {#v0.6.0-beta.2} -- nothing yet +- [x] (Added) _Back-end_ Native Open File on Windows & Mac OS (PR #1381) +- [x] (Added) _Back-end_ Native Open File with specific editor on Windows & Mac OS (PR #1381) +- [x] (Added) _Back-end_ AppSettings for Collections / Stacks and Open File (PR #1381) +- [x] (Breaking Change) _Back-end_ Rename UseLocalDesktopUi to UseLocalDesktop (PR #1381) +- [x] (Added) _Back-end_ ImageFormat = ExtensionRolesHelper.ImageFormat.directory (PR #1381) +- [x] (Added) _Back-end_ Add role to info api (PR #1381) +- [x] (Added) _Front-end_ Add settings for Open File (PR #1381) +- [x] (Added) _Back-end_ rename starsky core to starsky.project.web (PR #1381) +- [x] (Removed) _Back-end_ Remove /api/trash/detect-to-use-system-trash (PR #1381) +- [x] (Removed) _Back-end_ Remove verbose option in UI (setting is hidden now) (PR #1381) +- [x] (Added) _Front-end_ German translations (PR #1381) +- [x] (Added) _Front-end_ command + shift + k go to settings now (PR #1381) +- [x] (Removed) _App_ Removed overwrite of open app in desktop (replaced with native open file) + (PR #1381) +- [x] (Added) _App_ Add 'App Settings' to the menu (PR #1381) +- [x] (Added) _Front-end_ Add warning when opening a lot pictures at one: "Do you really want to + edit all of the selected photos?" (PR #1381) ## version 0.6.0-beta.1 - 2024-02-18 {#v0.6.0-beta.1} From e37f91dda8eca555161c2918c81788586e808fe7 Mon Sep 17 00:00:00 2001 From: Dion Date: Wed, 28 Feb 2024 20:38:48 +0100 Subject: [PATCH 125/125] fix docs build --- .../getting-started/docker/docker-compose.md | 134 ++++++--- .../getting-started/troubleshooting/index.md | 257 ++++++++++++------ .../getting-started/troubleshooting/logs.md | 52 ++-- .../troubleshooting/performance.md | 94 +++++-- .../getting-started/troubleshooting/sqlite.md | 29 +- 5 files changed, 394 insertions(+), 172 deletions(-) diff --git a/documentation/docs/getting-started/docker/docker-compose.md b/documentation/docs/getting-started/docker/docker-compose.md index 9e1feab812..d9a2723937 100644 --- a/documentation/docs/getting-started/docker/docker-compose.md +++ b/documentation/docs/getting-started/docker/docker-compose.md @@ -1,48 +1,66 @@ # Setup Using Docker Compose -With [Docker Compose](https://docs.docker.com/compose/), you [use a YAML file](../../developer-guide/technologies/yaml.md) +With [Docker Compose](https://docs.docker.com/compose/), +you [use a YAML file](../../developer-guide/technologies/yaml.md) to configure all application services so you can easily start them with a single command. -Before you proceed, make sure you have [Docker](https://store.docker.com/search?type=edition&offering=community) +Before you proceed, make sure you +have [Docker](https://store.docker.com/search?type=edition&offering=community) installed on your system. It is available for Mac, Linux, and Windows. ## You also could use the application without Docker or Docker compose -Docker is one way of using the application, it also possible to run it without Docker or Docker Compose. + +Docker is one way of using the application, it also possible to run it without Docker or Docker +Compose. ## Step 1 Configure -Download our [docker-compose.yml](https://raw.githubusercontent.com/qdraw/starsky/master/starsky/docker/compose/generic/docker-compose.yml) example +Download +our [docker-compose.yml](https://raw.githubusercontent.com/qdraw/starsky/master/starsky/docker/compose/generic/docker-compose.yml) +example (right click and *Save Link As...* or use `wget`) to a folder of your choice, -and change the [configuration](../config-options.md) as needed: +and change the [configuration](../configuration/config-options.md) as needed: ```bash wget https://raw.githubusercontent.com/qdraw/starsky/master/starsky/docker/compose/generic/docker-compose.yml ``` Commands on Linux may have to be prefixed with `sudo` when not running as root. -Note that this will point the home directory shortcut `~` to `/root` in the `volumes:` -section of your `docker-compose.yml`. Kernel security modules such as AppArmor and SELinux +Note that this will point the home directory shortcut `~` to `/root` in the `volumes:` +section of your `docker-compose.yml`. Kernel security modules such as AppArmor and SELinux have been [reported to cause issues](../troubleshooting/docker.md#kernel-security). -Ensure that your server has [at least 4 GB of swap](../troubleshooting/docker.md#adding-swap) configured so that +Ensure that your server has [at least 4 GB of swap](../troubleshooting/docker.md#adding-swap) +configured so that indexing doesn't cause restarts when there are memory usage spikes. - #### Database #### -Our example includes a pre-configured [MariaDB](https://mariadb.com/) database server. If you remove it +Our example includes a pre-configured [MariaDB](https://mariadb.com/) database server. If you remove +it and provide no other database server credentials, SQLite database files will be created in the -*storage* folder. Local [SSD storage is best](../troubleshooting/performance.md#storage) for databases of any kind. +*storage* folder. Local [SSD storage is best](../troubleshooting/performance.md#storage) for +databases of any kind. -Never [store database files](../troubleshooting/mariadb.md#corrupted-files) on an unreliable device such as a USB flash drive, SD card, or shared network folder. These may also have [unexpected file size limitations](https://thegeekpage.com/fix-the-file-size-exceeds-the-limit-allowed-and-cannot-be-saved/), which is especially problematic for databases that do not split data into smaller files. +Never [store database files](../troubleshooting/mariadb.md#corrupted-files) on an unreliable device +such as a USB flash drive, SD card, or shared network folder. These may also +have [unexpected file size limitations](https://thegeekpage.com/fix-the-file-size-exceeds-the-limit-allowed-and-cannot-be-saved/), +which is especially problematic for databases that do not split data into smaller files. > **TL;DR**
-It is not possible to change the password via `MARIADB_PASSWORD` after the database has been started -for the first time. Choosing a secure password is not essential if you don't [expose the database to other apps and hosts](../troubleshooting/mariadb.md#cannot-connect). -To enable [automatic schema updates](../troubleshooting/mariadb.md#auto-upgrade) after upgrading to a new major version, set `MARIADB_AUTO_UPGRADE` to a non-empty value in your `docker-compose.yml`. +> It is not possible to change the password via `MARIADB_PASSWORD` after the database has been +> started +> for the first time. Choosing a secure password is not essential if you +> don't [expose the database to other apps and hosts](../troubleshooting/mariadb.md#cannot-connect). +> To enable [automatic schema updates](../troubleshooting/mariadb.md#auto-upgrade) after upgrading +> to +> a new major version, set `MARIADB_AUTO_UPGRADE` to a non-empty value in your `docker-compose.yml`. #### Volumes #### -Since the app is running inside a container, you have to explicitly [mount the host folders](https://docs.docker.com/compose/compose-file/compose-file-v3/#volumes) you want to use. -The app won't be able to see folders that have not been mounted. That's an important security feature. +Since the app is running inside a container, you have to +explicitly [mount the host folders](https://docs.docker.com/compose/compose-file/compose-file-v3/#volumes) +you want to use. +The app won't be able to see folders that have not been mounted. That's an important security +feature. ##### /app/storageFolder ##### @@ -57,7 +75,8 @@ volumes: - "~/Pictures:/app/storageFolder" ``` -You may [mount any folder accessible from the host](https://docs.docker.com/compose/compose-file/compose-file-v3/#short-syntax-3) +You +may [mount any folder accessible from the host](https://docs.docker.com/compose/compose-file/compose-file-v3/#short-syntax-3) instead, including network drives. Additional directories can be mounted as subfolders of `/app/storageFolder`: @@ -76,32 +95,43 @@ volumes: ``` > **TL;DR**
-If *read-only mode* is enabled, all features that require write permission to the *originals/storageFolder* folder -are disabled, uploading and deleting files. Set `app__ReadOnlyFolders__0` to `"/"` -in `docker-compose.yml` for this. -> You can [mount a folder with the `:ro` flag](https://docs.docker.com/compose/compose-file/compose-file-v3/#short-syntax-3) +> If *read-only mode* is enabled, all features that require write permission to the +*originals/storageFolder* folder +> are disabled, uploading and deleting files. Set `app__ReadOnlyFolders__0` to `"/"` +> in `docker-compose.yml` for this. +> You +> +can [mount a folder with the `:ro` flag](https://docs.docker.com/compose/compose-file/compose-file-v3/#short-syntax-3) > to make Docker block write operations as well. ##### /app/thumbnailTempFolder ##### Thumbnails files are created in the *thumbnailTempFolder* folder: -- a *storage* folder mount must always be configured in your `docker-compose.yml` file so that you do not lose these files after a restart or upgrade -- never configure the *thumbnailTempFolder* folder to be inside the *thumbnailTempFolder* folder unless the name starts with a `.` to indicate that it is hidden -- we recommend placing the *thumbnailTempFolder* folder on a [local SSD drive](../troubleshooting/performance.md#storage) for best performance -- mounting [symbolic links](https://en.wikipedia.org/wiki/Symbolic_link) or using them inside the *thumbnailTempFolder* folder is currently not supported +- a *storage* folder mount must always be configured in your `docker-compose.yml` file so that you + do not lose these files after a restart or upgrade +- never configure the *thumbnailTempFolder* folder to be inside the *thumbnailTempFolder* folder + unless the name starts with a `.` to indicate that it is hidden +- we recommend placing the *thumbnailTempFolder* folder on + a [local SSD drive](../troubleshooting/performance.md#storage) for best performance +- mounting [symbolic links](https://en.wikipedia.org/wiki/Symbolic_link) or using them inside the + *thumbnailTempFolder* folder is currently not supported > **TL;DR**
-Should you later want to move your instance to another host, the easiest and most time-saving way is to copy the entire *storage* folder along with your originals and database. +> Should you later want to move your instance to another host, the easiest and most time-saving way +> is +> to copy the entire *storage* folder along with your originals and database. ##### import ##### -At the moment we don't have a import folder for docker, but you can use the CLI to import files or use web upload +At the moment we don't have a import folder for docker, but you can use the CLI to import files or +use web upload Import in a structured way that avoids duplicates: - imported files receive a canonical filename and will be organized by year and month -- never configure the *import* folder to be inside the *originals* folder, as this will cause a loop by importing already indexed files +- never configure the *import* folder to be inside the *originals* folder, as this will cause a loop + by importing already indexed files ### Step 2: Start the server ### @@ -112,20 +142,25 @@ Run this command to start the application and database services in the backgroun docker compose up -d ``` -*Note that our guides now use the new `docker compose` command by default. If your server does not yet support it, you can still use `docker-compose`.* +*Note that our guides now use the new `docker compose` command by default. If your server does not +yet support it, you can still use `docker-compose`.* Now open the Web UI by navigating to http://localhost:6470/. You should see a registration screen. You may change it on the [account settings page](../../features/accountmanagement.md). -Enabling [public mode](../config-options.md) will disable authentication. +Enabling [public mode](../configuration/config-options.md) will disable authentication. > **Info**
- It can be helpful to [keep Docker running in the foreground while debugging](../troubleshooting/docker.md#viewing-logs) so that log messages are displayed directly. To do this, omit the `-d` parameter when restarting. - Should the server already be running, or you see no errors, you may have started it - on a different host and/or port. There could also be an [issue with your browser, - [ad blocker, or firewall settings](../troubleshooting/index.md). +> It can be helpful +> +to [keep Docker running in the foreground while debugging](../troubleshooting/docker.md#viewing-logs) +> so that log messages are displayed directly. To do this, omit the `-d` parameter when restarting. +> Should the server already be running, or you see no errors, you may have started it +> on a different host and/or port. There could also be an [issue with your browser, +[ad blocker, or firewall settings](../troubleshooting/index.md). -The server port and other [config options](../config-options.md) can be changed in `docker-compose.yml` at any time. +The server port and other [config options](../configuration/config-options.md) can be changed +in `docker-compose.yml` at any time. Remember to restart the services for changes to take effect: ```bash @@ -135,12 +170,16 @@ docker compose up -d ### Step 3: Index Your Library ### -Our [First Steps πŸ‘£](../first-steps.md) tutorial guides you through the user interface and settings to ensure your library is indexed according to your individual preferences. +Our [First Steps πŸ‘£](../first-steps.md) tutorial guides you through the user interface and settings +to ensure your library is indexed according to your individual preferences. > **Note**
- Ensure [there is enough disk space available](../troubleshooting/docker.md#disk-space) for creating thumbnails and [verify filesystem permissions](../troubleshooting/docker.md) - before starting to index: Files in the *originals* folder must be readable, while the *storage* folder - including all subdirectories must be readable and writeable. +> Ensure [there is enough disk space available](../troubleshooting/docker.md#disk-space) for +> creating +> thumbnails and [verify filesystem permissions](../troubleshooting/docker.md) +> before starting to index: Files in the *originals* folder must be readable, while the *storage* +> folder +> including all subdirectories must be readable and writeable. Open the Web UI, go to *More* and click *Manual Sync* to start indexing your pictures. @@ -148,13 +187,20 @@ Easy, isn't it? ### Troubleshooting ### -If your server runs out of memory, the index is frequently locked, or other system resources are running low: +If your server runs out of memory, the index is frequently locked, or other system resources are +running low: -- [ ] Try [reducing the number of workers](../config-options.md#index-workers) by setting `app__maxDegreesOfParallelism` to a reasonably small value in `docker-compose.yml`, depending on the CPU performance and number of cores -- [ ] Make sure [your server has at least 4 GB of swap space](../troubleshooting/docker.md#adding-swap) so that indexing doesn't cause restarts when memory usage spikes; RAW image conversion and video transcoding are especially demanding +- [ ] Try [reducing the number of workers](../configuration/config-options.md#index-workers) by + setting `app__maxDegreesOfParallelism` to a reasonably small value in `docker-compose.yml`, + depending on the CPU performance and number of cores +- [ ] Make + sure [your server has at least 4 GB of swap space](../troubleshooting/docker.md#adding-swap) so + that indexing doesn't cause restarts when memory usage spikes; RAW image conversion and video + transcoding are especially demanding - [ ] If you are using SQLite, switch to MariaDB, which is better optimized for high concurrency -Other issues? Our [troubleshooting checklists](../troubleshooting/index.md) help you quickly diagnose and solve them. +Other issues? Our [troubleshooting checklists](../troubleshooting/index.md) help you quickly +diagnose and solve them. diff --git a/documentation/docs/getting-started/troubleshooting/index.md b/documentation/docs/getting-started/troubleshooting/index.md index b06593eda3..d55be58a08 100644 --- a/documentation/docs/getting-started/troubleshooting/index.md +++ b/documentation/docs/getting-started/troubleshooting/index.md @@ -1,10 +1,12 @@ # Troubleshooting Checklists -> You are welcome to ask for help in our [discussions](https://github.com/qdraw/starsky/discussions) page +> You are welcome to ask for help in our [discussions](https://github.com/qdraw/starsky/discussions) +> page ### Connection Fails ### -If [your browser](browsers.md) cannot connect to the Web UI even after waiting a few minutes, run this command to display +If [your browser](browsers.md) cannot connect to the Web UI even after waiting a few minutes, run +this command to display the last 100 log messages (omit `--tail=100` to see all): ```bash @@ -13,22 +15,39 @@ docker compose logs --tail=100 Before reporting a bug, whould you please the following things: -- [ ] Check the logs for messages like *disk full*, *disk quota exceeded*, *no space left on device*, *read-only file system*, *error creating path*, *wrong permissions*, *no route to host*, *connection failed*, and *killed*: - - [ ] If a service has been "killed" or otherwise automatically terminated, this points to a [memory problem](docker.md#adding-swap) (add swap and/or memory; remove or increase usage limits) - - [ ] In case the logs show "disk full", "quota exceeded", or "no space left" errors, either [the disk containing the *storage* folder is full](docker.md#disk-space) (add storage) or a disk usage limit is configured (remove or increase it) - - [ ] Errors such as "read-only file system", "error creating path", or "wrong permissions" indicate a [filesystem permission problem](docker.md) - - [ ] It may help to [add the `:z` mount flag to volumes](https://docs.docker.com/storage/bind-mounts/#configure-the-selinux-label) when using SELinux (RedHat/Fedora) - - [ ] Log messages that contain "no route to host" indicate a [problem with the database](mariadb.md) or Docker network configuration (follow our [examples](../docker/docker-compose.md)) -- [ ] Make sure you are using the correct protocol (default is `http`), port (default is `4823`), and host (default is `localhost`): - - [ ] Check if the server port you try to use [has been exposed](https://docs.docker.com/compose/compose-file/compose-file-v3/#ports) and [no firewall is blocking it](https://support.microsoft.com/en-us/windows/turn-microsoft-defender-firewall-on-or-off-ec0844f7-aebd-0583-67fe-601ecf5d774f) +- [ ] Check the logs for messages like *disk full*, *disk quota exceeded*, *no space left on + device*, *read-only file system*, *error creating path*, *wrong permissions*, *no route to host*, + *connection failed*, and *killed*: + - [ ] If a service has been "killed" or otherwise automatically terminated, this points to + a [memory problem](docker.md#adding-swap) (add swap and/or memory; remove or increase usage + limits) + - [ ] In case the logs show "disk full", "quota exceeded", or "no space left" errors, + either [the disk containing the *storage* folder is full](docker.md#disk-space) (add storage) + or a disk usage limit is configured (remove or increase it) + - [ ] Errors such as "read-only file system", "error creating path", or "wrong permissions" + indicate a [filesystem permission problem](docker.md) + - [ ] It may help + to [add the `:z` mount flag to volumes](https://docs.docker.com/storage/bind-mounts/#configure-the-selinux-label) + when using SELinux (RedHat/Fedora) + - [ ] Log messages that contain "no route to host" indicate + a [problem with the database](mariadb.md) or Docker network configuration (follow + our [examples](../docker/docker-compose.md)) +- [ ] Make sure you are using the correct protocol (default is `http`), port (default is `4823`), + and host (default is `localhost`): + - [ ] Check if the server port you try to + use [has been exposed](https://docs.docker.com/compose/compose-file/compose-file-v3/#ports) + and [no firewall is blocking it](https://support.microsoft.com/en-us/windows/turn-microsoft-defender-firewall-on-or-off-ec0844f7-aebd-0583-67fe-601ecf5d774f) - [ ] Only use `localhost` or `127.0.0.1` if the server is running on the same computer (host) - [ ] Avoid using IP addresses other than `127.0.0.1` directly, as they can change - - [ ] We recommend [configuring a local hostname](../../assets/getting-started-index-pihole-local-dns.png) to access other hosts on your network + - [ ] We + recommend [configuring a local hostname](../../assets/getting-started-index-pihole-local-dns.png) + to access other hosts on your network - [ ] Note that HTTP security headers will prevent the app from loading in a frame (override them) - [ ] Verify your computer meets the [system requirements](../readme.mdx#system-requirements) - [ ] Go through the [checklist for fatal server errors](#fatal-server-errors) -Should MariaDB get stuck in a restart loop and Starsky can't connect to it, this indicates a [memory](docker.md#adding-swap), +Should MariaDB get stuck in a restart loop and Starsky can't connect to it, this indicates +a [memory](docker.md#adding-swap), [filesystem](docker.md), or other [permission issue](docker.md#kernel-security): ``` @@ -38,7 +57,8 @@ starsky: dial tcp 172.18.0.2:3306: connect: no route to host mariadb: mysqld: Shutdown complete ``` -To enable [debug mode](../config-options.md), set `app__verbose` to `true` in the `environment:` section +To enable [debug mode](../configuration/config-options.md), set `app__verbose` to `true` in +the `environment:` section of the `starsky` service (or use the `-v` flag when running the `starsky` command directly): ```yaml @@ -48,8 +68,10 @@ services: app__verbose: "true" ``` -Then restart all services for the changes to take effect. It can be helpful to keep Docker running in the foreground -while debugging so that log messages are displayed directly. To do this, omit the `-d` parameter when restarting: +Then restart all services for the changes to take effect. It can be helpful to keep Docker running +in the foreground +while debugging so that log messages are displayed directly. To do this, omit the `-d` parameter +when restarting: ```bash docker compose stop @@ -57,12 +79,16 @@ docker compose up ``` !!! note "" - If you see no errors or no logs at all, you may have started the server on a different host - and/or port. There could also be an [issue with your browser](browsers.md), browser plugins, firewall settings, - or other tools you may have installed. +If you see no errors or no logs at all, you may have started the server on a different host +and/or port. There could also be an [issue with your browser](browsers.md), browser plugins, +firewall settings, +or other tools you may have installed. !!! tldr "" - The default [Docker Compose](https://docs.docker.com/compose/) config filename is `docker-compose.yml`. For simplicity, it doesn't need to be specified when running the `docker-compose` command in the same directory. Config files for other apps or instances should be placed in separate folders. +The default [Docker Compose](https://docs.docker.com/compose/) config filename +is `docker-compose.yml`. For simplicity, it doesn't need to be specified when running +the `docker-compose` command in the same directory. Config files for other apps or instances should +be placed in separate folders. ### Docker Doesn't Work ### @@ -87,44 +113,72 @@ docker compose up Fatal errors are often caused by one of the following conditions: - [ ] Your (virtual) server [disk is full](docker.md#disk-space) (add storage) -- [ ] You have accidentally [mounted the wrong folders](../docker/docker-compose.md#volumes) (update config and restart) -- [ ] There is disk space left, but a usage or the [inode limit](https://serverfault.com/questions/104986/what-is-the-maximum-number-of-files-a-file-system-can-contain) has been reached (change it) -- [ ] You are using a [filesystem or network drive with a file size limit](https://thegeekpage.com/fix-the-file-size-exceeds-the-limit-allowed-and-cannot-be-saved/) (change settings or storage) -- [ ] The *storage* folder [is not writable or mounted read-only](docker.md) (change [permissions](docker.md)) -- [ ] [Symbolic links](https://en.wikipedia.org/wiki/Symbolic_link) were mounted or used within a *storage* folder (replace with actual paths) +- [ ] You have accidentally [mounted the wrong folders](../docker/docker-compose.md#volumes) (update + config and restart) +- [ ] There is disk space left, but a usage or + the [inode limit](https://serverfault.com/questions/104986/what-is-the-maximum-number-of-files-a-file-system-can-contain) + has been reached (change it) +- [ ] You are using + a [filesystem or network drive with a file size limit](https://thegeekpage.com/fix-the-file-size-exceeds-the-limit-allowed-and-cannot-be-saved/) ( + change settings or storage) +- [ ] The *storage* folder [is not writable or mounted read-only](docker.md) ( + change [permissions](docker.md)) +- [ ] [Symbolic links](https://en.wikipedia.org/wiki/Symbolic_link) were mounted or used within a + *storage* folder (replace with actual paths) - [ ] The [server is low on memory](../readme.mdx#system-requirements) (add memory) - [ ] You didn't [configure at least 4 GB of swap space](docker.md#adding-swap) (add swap) -- [ ] High-resolution panoramic images require [additional memory](performance.md#memory) above the recommended minimum (add more swap or memory) +- [ ] High-resolution panoramic images require [additional memory](performance.md#memory) above the + recommended minimum (add more swap or memory) - [ ] The server CPU is overheating (improve cooling) - [ ] The server has an outdated operating system that is not fully compatible (update) - [ ] The server hardware is defective and causes random panics (test on another server) -- [ ] The [database server](mariadb.md) is not running, [incompatible](../readme.mdx#databases), or misconfigured (start, upgrade, or [fix it](mariadb.md)) -- [ ] You've [upgraded the MariaDB server](mariadb.md#version-upgrade) without running `mariadb-upgrade` -- [ ] Files are [stored on an unreliable device such as a USB flash drive or a shared network folder](mariadb.md#corrupted-files) +- [ ] The [database server](mariadb.md) is not running, [incompatible](../readme.mdx#databases), or + misconfigured (start, upgrade, or [fix it](mariadb.md)) +- [ ] You've [upgraded the MariaDB server](mariadb.md#version-upgrade) without + running `mariadb-upgrade` +- [ ] Files + are [stored on an unreliable device such as a USB flash drive or a shared network folder](mariadb.md#corrupted-files) - [ ] There are network problems caused by a bad configuration, firewall, or unstable connection -- [ ] [Kernel security modules](docker.md#kernel-security) such as [AppArmor](https://wiki.ubuntu.com/AppArmor) and [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux) are blocking permissions -- [ ] Your Raspberry Pi has not been configured according to our [recommendations](../raspberry-pi.md#system-requirements) - -We recommend checking your [Docker Logs](docker.md#viewing-logs) for messages like *disk full*, *disk quota exceeded*, -*no space left on device*, *read-only file system*, *error creating path*, *wrong permissions*, *no route to host*, *connection failed*, and *killed*: - -- [ ] If a service has been "killed" or otherwise automatically terminated, this points to a [memory problem](docker.md#adding-swap) (add swap and/or memory; remove or increase usage limits) -- [ ] In case the logs show "disk full", "quota exceeded", or "no space left" errors, either [the disk containing the *storage* folder is full](docker.md#disk-space) (add storage) or a disk usage limit is configured (remove or increase it) -- [ ] Errors such as "read-only file system", "error creating path", or "wrong permissions" indicate a [filesystem permission problem](docker.md) -- [ ] Log messages that contain "no route to host" indicate a [problem with the database](mariadb.md) or network configuration (follow our [examples](../docker/docker-compose.md)) - -*Start a full rescan if necessary, for example, if it looks like [thumbnails](index.md#broken-thumbnails) or [pictures are missing](index.md#missing-pictures).* +- [ ] [Kernel security modules](docker.md#kernel-security) such + as [AppArmor](https://wiki.ubuntu.com/AppArmor) + and [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux) are blocking permissions +- [ ] Your Raspberry Pi has not been configured according to + our [recommendations](../raspberry-pi.md#system-requirements) + +We recommend checking your [Docker Logs](docker.md#viewing-logs) for messages like *disk full*, +*disk quota exceeded*, +*no space left on device*, *read-only file system*, *error creating path*, *wrong permissions*, *no +route to host*, *connection failed*, and *killed*: + +- [ ] If a service has been "killed" or otherwise automatically terminated, this points to + a [memory problem](docker.md#adding-swap) (add swap and/or memory; remove or increase usage + limits) +- [ ] In case the logs show "disk full", "quota exceeded", or "no space left" errors, + either [the disk containing the *storage* folder is full](docker.md#disk-space) (add storage) or a + disk usage limit is configured (remove or increase it) +- [ ] Errors such as "read-only file system", "error creating path", or "wrong permissions" indicate + a [filesystem permission problem](docker.md) +- [ ] Log messages that contain "no route to host" indicate + a [problem with the database](mariadb.md) or network configuration (follow + our [examples](../docker/docker-compose.md)) + +*Start a full rescan if necessary, for example, if it looks +like [thumbnails](index.md#broken-thumbnails) or [pictures are missing](index.md#missing-pictures).* ### App Not Loading ### -If the app doesn't load in your browser when you navigate to the server URL, you can [check the browser console](browsers.md#getting-error-details) -for helpful errors and warnings. Sometimes you just need to wait a moment, for example, if you are using a slow wireless +If the app doesn't load in your browser when you navigate to the server URL, you +can [check the browser console](browsers.md#getting-error-details) +for helpful errors and warnings. Sometimes you just need to wait a moment, for example, if you are +using a slow wireless connection or the server was started only a few seconds ago. In case this does not help: - [ ] You are using an [incompatible browser](browsers.md) (try another browser) - [ ] JavaScript is disabled in your browser settings, so you only see the splash screen (enable it) - [ ] JavaScript was disabled by a browser plugin (disable it or add an exception) -- [ ] Your browser cannot communicate properly with the server, e.g. because a [Reverse Proxy](../proxies/nginx.md), VPN, or CDN is configured incorrectly (check its configuration and try without) +- [ ] Your browser cannot communicate properly with the server, e.g. because + a [Reverse Proxy](../proxies/nginx.md), VPN, or CDN is configured incorrectly (check its + configuration and try without) - [ ] HTTP security headers prevent the app from loading in a frame (override them) - [ ] An ad blocker or other plugins block requests (disable them or add an exception) - [ ] There is a problem with your network connection (test if other sites work) @@ -132,11 +186,13 @@ connection or the server was started only a few seconds ago. In case this does n ### Missing Pictures ### -If you have indexed your library and some images or videos are missing, first [check *Library > Errors* for errors and warnings](logs.md). +If you have indexed your library and some images or videos are missing, first check the logs In case the application logs don't contain anything helpful: -- [ ] The files exceed the [size limit in megabyte or the resolution limit in megapixels](../config-options.md#storage) -- [ ] The files have [bad filesystem permissions or the wrong owner](docker.md), so they cannot be opened +- [ ] The files exceed + the [size limit in megabyte or the resolution limit in megapixels](../configuration/config-options.md#storage) +- [ ] The files have [bad filesystem permissions or the wrong owner](docker.md), so they cannot be + opened - [ ] The file type is generally unsupported - [ ] The file type is generally supported, but a specific feature or codec is missing - [ ] The indexer has skipped the files because they are exact duplicates @@ -145,87 +201,130 @@ In case the application logs don't contain anything helpful: - [ ] The file is broken, e.g. because of *short Huffman data* (try to fix it) - [ ] [Your (virtual) server disk is full](docker.md#disk-space) (add storage) - [ ] [The *storage* folder is not writable](docker.md) (change [permissions](docker.md)) - - [ ] A disk usage or the [inode limit](https://serverfault.com/questions/104986/what-is-the-maximum-number-of-files-a-file-system-can-contain) has been reached (remove or increase it) + - [ ] A disk usage or + the [inode limit](https://serverfault.com/questions/104986/what-is-the-maximum-number-of-files-a-file-system-can-contain) + has been reached (remove or increase it) - [ ] Multiple files were [stacked](../../features/stacks.md) based on their metadata or file names - [ ] You try to index a shared drive on a remote server, but the server is offline -- [ ] The indexer has crashed because you didn't [configure at least 4 GB of swap](docker.md#adding-swap) +- [ ] The indexer has crashed because you + didn't [configure at least 4 GB of swap](docker.md#adding-swap) - [ ] Somebody has deleted files without telling you - [ ] You are connected to the wrong server, VPN, CDN, or a DNS record has not been updated yet -*Depending on the cause of the problem, you may need to perform a full rescan once the issue is resolved.* +*Depending on the cause of the problem, you may need to perform a full rescan once the issue is +resolved.* #### Zip Archives #### -When you try to download multiple pictures and find that some are missing from the resulting zip archive, or you get the error message "No files available for download," your index may be incomplete or out of date (for example, after updating Starsky). A complete rescan of your library may solve the problem. +When you try to download multiple pictures and find that some are missing from the resulting zip +archive, or you get the error message "No files available for download," your index may be +incomplete or out of date (for example, after updating Starsky). A complete rescan of your library +may solve the problem. ### Wrong Search Results ### If search results are incorrect, for example, in the wrong order or not filtered properly: - [ ] Indexing is still in progress and has not been completed yet -- [ ] You need to [re-index your pictures](mariadb.md#complete-rescan), for example after updating Starsky -- [ ] Previously [failed migrations must be re-run](mariadb.md#incompatible-schema) to update the index schema +- [ ] You need to [re-index your pictures](mariadb.md#complete-rescan), for example after updating + Starsky +- [ ] Previously [failed migrations must be re-run](mariadb.md#incompatible-schema) to update the + index schema - [ ] The database server is [incompatible or needs to be updated](../readme.mdx#databases) -*It may be a bug if you cannot find any other reasons, such as a local configuration problem or a misunderstanding in how the software works. Please note that reports must be reproducible in order for us to provide a solution.* +*It may be a bug if you cannot find any other reasons, such as a local configuration problem or a +misunderstanding in how the software works. Please note that reports must be reproducible in order +for us to provide a solution.* ### Broken Thumbnails ### -If some pictures have broken or missing thumbnails, first [check *Library > Errors* for errors and warnings](logs.md). +If some pictures have broken or missing thumbnails, first [check the logs](logs.md). In case the application logs don't contain anything helpful: - [ ] The issue can be resolved by reloading the page or clearing the browser cache - [ ] You browse non-JPEG files in *Library > Originals* which have an icon but no preview - [ ] [Your (virtual) server disk is full](docker.md#disk-space) (add storage) -- [ ] A disk usage or the [inode limit](https://serverfault.com/questions/104986/what-is-the-maximum-number-of-files-a-file-system-can-contain) has been reached (remove or increase it) -- [ ] The *storage* folder [is not writable or mounted read-only](docker.md) (change [permissions](docker.md)) +- [ ] A disk usage or + the [inode limit](https://serverfault.com/questions/104986/what-is-the-maximum-number-of-files-a-file-system-can-contain) + has been reached (remove or increase it) +- [ ] The *storage* folder [is not writable or mounted read-only](docker.md) ( + change [permissions](docker.md)) - [ ] Files were deleted manually, for example to free up disk space - [ ] Files can't be opened, e.g. because the file system permissions have been changed - [ ] Files are stored on an unreliable device such as a USB flash drive or a shared network folder -- [ ] Some thumbnails could not be created because you didn't [configure at least 4 GB of swap](docker.md#adding-swap) -- [ ] Your browser cannot communicate properly with the server, e.g. because a [Reverse Proxy](../proxies/nginx.md), VPN, or CDN is configured incorrectly (check its configuration and try without) +- [ ] Some thumbnails could not be created because you + didn't [configure at least 4 GB of swap](docker.md#adding-swap) +- [ ] Your browser cannot communicate properly with the server, e.g. because + a [Reverse Proxy](../proxies/nginx.md), VPN, or CDN is configured incorrectly (check its + configuration and try without) - [ ] Your proxy, router, or firewall has a request rate limit, so some requests fail - [ ] There are other network problems caused by a firewall, router, or unstable connection - [ ] An ad blocker or other plugins block requests (disable them or add an exception) - [ ] You are connected to the wrong server, VPN, CDN, or a DNS record has not been updated yet -We also recommend checking your [Docker Logs](docker.md#viewing-logs) for messages like *disk full*, *disk quota exceeded*, -*no space left on device*, *read-only file system*, *error creating path*, *wrong permissions*, and *killed*: +We also recommend checking your [Docker Logs](docker.md#viewing-logs) for messages like *disk full*, +*disk quota exceeded*, +*no space left on device*, *read-only file system*, *error creating path*, *wrong permissions*, and +*killed*: - [ ] If a service has been "killed" or otherwise automatically terminated, this points to a -memory problem -- [ ] In case the logs show "disk full", "quota exceeded", or "no space left" errors, either [the disk containing the *storage* folder is full](docker.md#disk-space) (add storage) or a disk usage limit is configured (remove or increase it) -- [ ] Errors such as "read-only file system", "error creating path", or "wrong permissions" indicate a [filesystem permission problem](docker.md) + memory problem +- [ ] In case the logs show "disk full", "quota exceeded", or "no space left" errors, + either [the disk containing the *storage* folder is full](docker.md#disk-space) (add storage) or a + disk usage limit is configured (remove or increase it) +- [ ] Errors such as "read-only file system", "error creating path", or "wrong permissions" indicate + a [filesystem permission problem](docker.md) -*Depending on the cause of the problem, you may need to perform a full rescan once the issue is resolved.* +*Depending on the cause of the problem, you may need to perform a full rescan once the issue is +resolved.* ### Videos Don't Play ### If videos do not play and/or you only see a white/black area when you open a video: -- [ ] You are using an [incompatible browser](browsers.md), e.g. without AVC support (try another browser) -- [ ] AVC support or related JavaScript features have been disabled in your browser (check the settings and try another browser) +- [ ] You are using an [incompatible browser](browsers.md), e.g. without AVC support (try another + browser) +- [ ] AVC support or related JavaScript features have been disabled in your browser (check the + settings and try another browser) - [ ] An ad blocker or other plugins block requests (disable them or add an exception) - [ ] [Your (virtual) server disk is full](docker.md#disk-space) (add storage) -- [ ] A disk usage or the [inode limit](https://serverfault.com/questions/104986/what-is-the-maximum-number-of-files-a-file-system-can-contain) has been reached (remove or increase it) -- [ ] The *storage* folder [is not writable or mounted read-only](docker.md) (change [permissions](docker.md)) -- [ ] Files are stored on an unreliable device such as a USB flash drive or a shared network folder (check if the files are accessible) -- [ ] Your browser cannot communicate properly with the server, e.g. because a [Reverse Proxy](../proxies/nginx.md), VPN, or CDN is configured incorrectly (check its configuration and try without) -- [ ] There are other network problems caused by a proxy, firewall, or unstable connection (try a direct connection) +- [ ] A disk usage or + the [inode limit](https://serverfault.com/questions/104986/what-is-the-maximum-number-of-files-a-file-system-can-contain) + has been reached (remove or increase it) +- [ ] The *storage* folder [is not writable or mounted read-only](docker.md) ( + change [permissions](docker.md)) +- [ ] Files are stored on an unreliable device such as a USB flash drive or a shared network + folder (check if the files are accessible) +- [ ] Your browser cannot communicate properly with the server, e.g. because + a [Reverse Proxy](../proxies/nginx.md), VPN, or CDN is configured incorrectly (check its + configuration and try without) +- [ ] There are other network problems caused by a proxy, firewall, or unstable connection (try a + direct connection) - [ ] You are connected to the wrong server, VPN, CDN, or a DNS record has not been updated yet -We recommend that you check your [Docker Logs](docker.md#viewing-logs) and [the browser console](browsers.md#getting-error-details) -for messages related to *HTTP requests*, *permissions*, *security*, *FFmpeg*, *videos*, and *file conversion*. +We recommend that you check your [Docker Logs](docker.md#viewing-logs) +and [the browser console](browsers.md#getting-error-details) +for messages related to *HTTP requests*, *permissions*, *security*, *FFmpeg*, *videos*, and *file +conversion*. Please note: -1. Not all [video and audio formats](https://caniuse.com/?search=video%20format) can be [played with every browser](browsers.md). For example, [AAC](https://caniuse.com/aac "Advanced Audio Coding") - the default audio codec for [MPEG-4 AVC / H.264](https://caniuse.com/avc "Advanced Video Coding") - is supported natively in Chrome, Safari, and Edge, while it is only optionally supported by the OS in Firefox and Opera. -2. HEVC/H.265 video files can have a `.mp4` file extension too, which is often associated with AVC only. This is because MP4 is a *container* format, meaning that the actual video content may be compressed with H.264, H.265, or something else. The file extension doesn't really tell you anything other than that it's probably a video file. +1. Not all [video and audio formats](https://caniuse.com/?search=video%20format) can + be [played with every browser](browsers.md). For + example, [AAC](https://caniuse.com/aac "Advanced Audio Coding") - the default audio codec + for [MPEG-4 AVC / H.264](https://caniuse.com/avc "Advanced Video Coding") - is supported natively + in Chrome, Safari, and Edge, while it is only optionally supported by the OS in Firefox and + Opera. +2. HEVC/H.265 video files can have a `.mp4` file extension too, which is often associated with AVC + only. This is because MP4 is a *container* format, meaning that the actual video content may be + compressed with H.264, H.265, or something else. The file extension doesn't really tell you + anything other than that it's probably a video file. !!! info "" - **We kindly ask you not to report bugs via *GitHub Issues* unless you are certain to have found a fully reproducible and previously unreported issue that must be fixed directly in the app.** - Ask for technical support if you need help, it could be a local - configuration problem, or a misunderstanding in how the software works. +**We kindly ask you not to report bugs via *GitHub Issues* unless you are certain to have found a +fully reproducible and previously unreported issue that must be fixed directly in the app.** +Ask for technical support if you need help, it could be a local +configuration problem, or a misunderstanding in how the software works. ## Used words @@ -239,7 +338,7 @@ Please note: *[RAW]: image format that contains unprocessed sensor data *[URL]: Web Address *[FFmpeg]: transcodes video files -*[HEVC]: High Efficiency Video Coding / H.265 +*[HEVC]: High Efficiency Video Coding / H.265 *[SQLite]: self-contained, serverless SQL database *[swap]: substitute for physical memory *[host]: Computer, Cloud Server, or VM that runs Starsky diff --git a/documentation/docs/getting-started/troubleshooting/logs.md b/documentation/docs/getting-started/troubleshooting/logs.md index e6437a8732..06768535d7 100644 --- a/documentation/docs/getting-started/troubleshooting/logs.md +++ b/documentation/docs/getting-started/troubleshooting/logs.md @@ -5,34 +5,43 @@ The Electron stores it's cache in these folders: Windows: + ``` C:\Users\\AppData\Roaming\starsky\logs ``` Linux: + ``` ~/.config/starsky/logs ``` OS X: + ``` ~/Library/Application\ Support/starsky/logs ``` ## "Browser" - -If you [have a frontend issue](browsers.md), it is often helpful to check the browser console for errors and warnings. -A console is available in all modern browsers and can be activated via keyboard shortcuts or the browser menu. -Problems with the user interface can be caused by a bug or an [incompatible browser](browsers.md#try-another-browser): -Some [features may not be supported](https://caniuse.com/) by non-standard browsers, as well as nightly, unofficial, +If you [have a frontend issue](browsers.md), it is often helpful to check the browser console for +errors and warnings. +A console is available in all modern browsers and can be activated via keyboard shortcuts or the +browser menu. + +Problems with the user interface can be caused by a bug or +an [incompatible browser](browsers.md#try-another-browser): +Some [features may not be supported](https://caniuse.com/) by non-standard browsers, as well as +nightly, unofficial, or outdated versions. -*In case you don't see any log messages, try reloading the page, as the problem may occur while the page is loading.* +*In case you don't see any log messages, try reloading the page, as the problem may occur while the +page is loading.* **Chrome, Chromium, and Edge** -- press ⌘+Option+J (Mac) or Ctrl+Shift+J (Windows, Linux, Chrome OS) to go directly to the Developer Tools +- press ⌘+Option+J (Mac) or Ctrl+Shift+J (Windows, Linux, Chrome OS) to go directly to the Developer + Tools - or, navigate to *More tools* > *Developer tools* in the browser menu and open the *Console* tab **Firefox** @@ -60,7 +69,8 @@ Run this command to display the last 100 log messages (omit `--tail=100` to see docker compose logs --tail=100 ``` -To enable [debug mode](../config-options.md), set `app__verbose` to `true` in the `environment:` section +To enable [debug mode](../configuration/config-options.md), set `app__verbose` to `true` in +the `environment:` section of the `starsky` service (or use the `-v` flag when running the `starsky` command directly): ```yaml @@ -70,18 +80,24 @@ services: app__verbose: "true" ``` -Then restart all services for the changes to take effect. It can be helpful to keep Docker running in the foreground -while debugging so that log messages are displayed directly. To do this, omit the `-d` parameter when restarting: +Then restart all services for the changes to take effect. It can be helpful to keep Docker running +in the foreground +while debugging so that log messages are displayed directly. To do this, omit the `-d` parameter +when restarting: ```bash docker compose stop docker compose up ``` - -> **Note**
- If you see no errors or no logs at all, you may have started the server on a different host - and/or port. There could also be an [issue with your browser](browsers.md), browser plugins, firewall settings, - or other tools you may have installed. - -> **TL;DR**
- The default [Docker Compose](https://docs.docker.com/compose/) config filename is `docker-compose.yml`. For simplicity, it doesn't need to be specified when running the `docker-compose` command in the same directory. Config files for other apps or instances should be placed in separate folders. + +> **Note**
+> If you see no errors or no logs at all, you may have started the server on a different host +> and/or port. There could also be an [issue with your browser](browsers.md), browser plugins, +> firewall settings, +> or other tools you may have installed. + +> **TL;DR**
+> The default [Docker Compose](https://docs.docker.com/compose/) config filename +> is `docker-compose.yml`. For simplicity, it doesn't need to be specified when running +> the `docker-compose` command in the same directory. Config files for other apps or instances should +> be placed in separate folders. diff --git a/documentation/docs/getting-started/troubleshooting/performance.md b/documentation/docs/getting-started/troubleshooting/performance.md index 2ebda9a1c9..18e77c6ae7 100644 --- a/documentation/docs/getting-started/troubleshooting/performance.md +++ b/documentation/docs/getting-started/troubleshooting/performance.md @@ -2,11 +2,16 @@ ## MariaDB ## -The [InnoDB buffer pool](https://mariadb.com/kb/en/innodb-buffer-pool/) serves as a cache for data and indexes. -It is a key component for optimizing MariaDB performance. Its size should be as large as possible to keep frequently +The [InnoDB buffer pool](https://mariadb.com/kb/en/innodb-buffer-pool/) serves as a cache for data +and indexes. +It is a key component for optimizing MariaDB performance. Its size should be as large as possible to +keep frequently used data in memory and reduce disk I/O - typically the biggest bottleneck. -By default, the buffer pool size is between 128 MB and 512 MB, depending on which configuration example you use. You can change it with the `--innodb-buffer-pool-size` command parameter in the `mariadb:` section of your `docker-compose.yml`. `M` stands for Megabyte, `G` for Gigabyte. Do not use spaces. +By default, the buffer pool size is between 128 MB and 512 MB, depending on which configuration +example you use. You can change it with the `--innodb-buffer-pool-size` command parameter in +the `mariadb:` section of your `docker-compose.yml`. `M` stands for Megabyte, `G` for Gigabyte. Do +not use spaces. If your server has plenty of physical memory, we recommend increasing the size to 1 or 2 GB: @@ -16,17 +21,28 @@ services: command: mysqld --innodb-buffer-pool-size=1G ... ``` -As a rule of thumb, [`Innodb_buffer_pool_pages_free`](https://mariadb.com/kb/en/innodb-status-variables/#innodb_buffer_pool_pages_free) should never be [less than 5% of the total pages](https://vettabase.com/blog/is-innodb-buffer-pool-big-enough/). -You can run the following SQL statement, for example using the [`mariadb` command](https://mariadb.com/kb/en/mysql-command-line-client/) in a terminal, to display the number of free pages and other InnoDB-related status information: +As a rule of +thumb, [`Innodb_buffer_pool_pages_free`](https://mariadb.com/kb/en/innodb-status-variables/#innodb_buffer_pool_pages_free) +should never +be [less than 5% of the total pages](https://vettabase.com/blog/is-innodb-buffer-pool-big-enough/). +You can run the following SQL statement, for example using +the [`mariadb` command](https://mariadb.com/kb/en/mysql-command-line-client/) in a terminal, to +display the number of free pages and other InnoDB-related status information: ```SQL SHOW GLOBAL STATUS LIKE 'Innodb_buffer%'; ``` -Advanced users may adjust additional parameters to further improve performance. Tools such as the [mysqltuner.pl](https://github.com/major/MySQLTuner-perl) script can provide helpful recommendations for this. +Advanced users may adjust additional parameters to further improve performance. Tools such as +the [mysqltuner.pl](https://github.com/major/MySQLTuner-perl) script can provide helpful +recommendations for this. !!! info "Windows and macOS" - If you are using *Docker Desktop* on Windows or macOS, remember to increase the [total memory available](../../assets/getting-started-docker-resources-advanced.jpg) for Docker services. Otherwise, they may run out of resources and cannot benefit from a larger cache size. In case Starsky and MariaDB are running in a virtual machine, its memory size should be increased as well. Restart for changes to take effect. +If you are using *Docker Desktop* on Windows or macOS, remember to increase +the [total memory available](../../assets/getting-started-docker-resources-advanced.jpg) for Docker +services. Otherwise, they may run out of resources and cannot benefit from a larger cache size. In +case Starsky and MariaDB are running in a virtual machine, its memory size should be increased as +well. Restart for changes to take effect. ## Windows ## @@ -34,7 +50,8 @@ Advanced users may adjust additional parameters to further improve performance. ## Storage ## -Local Solid-State Drives (SSDs) are [best for databases](https://mariadb.com/de/resources/blog/how-to-tune-mariadb-write-performance/) +Local Solid-State Drives (SSDs) +are [best for databases](https://mariadb.com/de/resources/blog/how-to-tune-mariadb-write-performance/) of any kind: - database performance extremely benefits from high throughput which HDDs can't provide @@ -42,49 +59,76 @@ of any kind: - due to the HDD seek time, HDDs only support 5% of the reads per second of SSDs - the cost savings from using slow hard disks are minimal -Switching to SSDs makes a big difference, especially for write operations and when the read cache is not +Switching to SSDs makes a big difference, especially for write operations and when the read cache is +not big enough or can't be used. > **note**
- Never store database files on an unreliable device such as a USB flash drive, SD card, or shared network folder. These may also have [unexpected file size limitations](https://thegeekpage.com/fix-the-file-size-exceeds-the-limit-allowed-and-cannot-be-saved/), which is especially problematic for databases that do not split data into smaller files. +> Never store database files on an unreliable device such as a USB flash drive, SD card, or shared +> network folder. These may also +> have [unexpected file size limitations](https://thegeekpage.com/fix-the-file-size-exceeds-the-limit-allowed-and-cannot-be-saved/), +> which is especially problematic for databases that do not split data into smaller files. ## Memory ## -Indexing large photo and video collections benefits from plenty of memory for [caching](#mariadb) and processing large media files. -Ideally, the amount of RAM should match the number of physical CPU cores. If not, reduce the number of workers +Indexing large photo and video collections benefits from plenty of memory for [caching](#mariadb) +and processing large media files. +Ideally, the amount of RAM should match the number of physical CPU cores. If not, reduce the number +of workers as [explained below](#troubleshooting). - ## Server CPU ## -Last but not least, performance can be limited by your server CPU. If you've tried everything else, then only moving +Last but not least, performance can be limited by your server CPU. If you've tried everything else, +then only moving your instance to a more powerful device or cloud server may help. -Be aware that most [NAS devices](https://kb.synology.com/en-us/DSM/tutorial/What_kind_of_CPU_does_my_NAS_have) are -optimized for minimal power consumption and low production costs. Although their hardware gets faster with each generation, -[benchmarks](https://www.google.com/search?q=cpu+benchmarks) show that even 8-year-old standard desktop CPUs like the [Intel Core i3-4130](https://www.cpubenchmark.net/compare/Intel-Pentium-J3710-vs-Intel-i3-4130/2784vs2015) are often many times faster: +Be aware that +most [NAS devices](https://kb.synology.com/en-us/DSM/tutorial/What_kind_of_CPU_does_my_NAS_have) are +optimized for minimal power consumption and low production costs. Although their hardware gets +faster with each generation, +[benchmarks](https://www.google.com/search?q=cpu+benchmarks) show that even 8-year-old standard +desktop CPUs like +the [Intel Core i3-4130](https://www.cpubenchmark.net/compare/Intel-Pentium-J3710-vs-Intel-i3-4130/2784vs2015) +are often many times faster: ![CPU Benchmark](../../assets/getting-started-troubleshooting-performance-passmark-cpu.svg) ## Legacy Hardware ## -It is a known issue that the user interface and backend operations, especially face recognition, can be slow or even crash on older hardware due to a lack of resources. Like most applications, Starsky has certain requirements and our development process does not include testing on unsupported or unusual hardware. +It is a known issue that the user interface and backend operations, especially face recognition, can +be slow or even crash on older hardware due to a lack of resources. Like most applications, Starsky +has certain requirements and our development process does not include testing on unsupported or +unusual hardware. -In many cases, performance can be improved through optimizations. Since these can prove to be very time-consuming and cost-intensive in practice, users and developers must decide on a case-by-case basis whether this provides sufficient benefit in relation to the costs or whether the use of more powerful hardware is faster and cheaper overall. +In many cases, performance can be improved through optimizations. Since these can prove to be very +time-consuming and cost-intensive in practice, users and developers must decide on a case-by-case +basis whether this provides sufficient benefit in relation to the costs or whether the use of more +powerful hardware is faster and cheaper overall. -We kindly ask you not to open a problem report on GitHub Issues for poor performance on older hardware until a full cause and feasibility analysis has been performed. [GitHub Discussions](https://github.com/qdraw/starsky/discussions) or any of our other public forums and communities are great places to start a discussion. +We kindly ask you not to open a problem report on GitHub Issues for poor performance on older +hardware until a full cause and feasibility analysis has been +performed. [GitHub Discussions](https://github.com/qdraw/starsky/discussions) or any of our other +public forums and communities are great places to start a discussion. -That being said, one of the advantages of open-source software is that users can submit [pull requests](https://github.com/qdraw/starsky) with performance and other enhancements they would like to see implemented. This will result in a much faster solution than waiting for a core team member to remotely analyze your problem and then provide a fix. +That being said, one of the advantages of open-source software is that users can +submit [pull requests](https://github.com/qdraw/starsky) with performance and other enhancements +they would like to see implemented. This will result in a much faster solution than waiting for a +core team member to remotely analyze your problem and then provide a fix. ## Troubleshooting ## -If your server runs out of memory, the index is frequently locked, or other system resources are running low: +If your server runs out of memory, the index is frequently locked, or other system resources are +running low: -- [ ] Try [reducing the number of workers](../config-options.md#index-workers) by setting `app__maxDegreesOfParallelism` to a reasonably small value in `docker-compose.yml`, depending on the CPU performance and number of cores -- [ ] Make sure [your server has at least 4 GB of swap space](docker.md#adding-swap) so that indexing doesn't cause restarts when memory usage spikes; RAW image conversion and video transcoding are especially demanding +- [ ] Try [reducing the number of workers](../configuration/config-options.md#index-workers) by + setting `app__maxDegreesOfParallelism` to a reasonably small value in `docker-compose.yml`, + depending on the CPU performance and number of cores +- [ ] Make sure [your server has at least 4 GB of swap space](docker.md#adding-swap) so that + indexing doesn't cause restarts when memory usage spikes; RAW image conversion and video + transcoding are especially demanding - [ ] If you are using SQLite, switch to MariaDB, which is better optimized for high concurrency Other issues? Our [troubleshooting checklists](index.md) help you quickly diagnose and solve them. - *[SQLite]: self-contained, serverless SQL database diff --git a/documentation/docs/getting-started/troubleshooting/sqlite.md b/documentation/docs/getting-started/troubleshooting/sqlite.md index 61e432a691..15cef150ad 100644 --- a/documentation/docs/getting-started/troubleshooting/sqlite.md +++ b/documentation/docs/getting-started/troubleshooting/sqlite.md @@ -2,23 +2,40 @@ ## Bad Performance -If you have only a few images, concurrent users, and CPU cores, [SQLite](https://www.sqlite.org/) may seem faster compared to full-fledged database servers like [MariaDB](https://mariadb.com/). +If you have only a few images, concurrent users, and CPU cores, [SQLite](https://www.sqlite.org/) +may seem faster compared to full-fledged database servers like [MariaDB](https://mariadb.com/). -This changes as the index grows and the number of concurrent requests increases. The way MariaDB handles multiple queries is completely different and optimized for high concurrency, while SQLite, for example, locks the index on updates so that other operations have to wait. In the worst case, this can lead to locking errors and timeouts during indexing - especially when combined with a slow disk or network storage. +This changes as the index grows and the number of concurrent requests increases. The way MariaDB +handles multiple queries is completely different and optimized for high concurrency, while SQLite, +for example, locks the index on updates so that other operations have to wait. In the worst case, +this can lead to locking errors and timeouts during indexing - especially when combined with a slow +disk or network storage. -The biggest advantage of SQLite is that you don't need to run a separate database server. This can be very useful for testing and works well if you only have a few thousand files to index. If you are looking for scalability and high performance, it is not a good choice. +The biggest advantage of SQLite is that you don't need to run a separate database server. This can +be very useful for testing and works well if you only have a few thousand files to index. If you are +looking for scalability and high performance, it is not a good choice. [Get MariaDB Performance Tips β€Ί](performance.md#mariadb) ## Locking Errors -If you use [traditional hard drives instead of SSDs](performance.md#storage), you will find that Starsky frequently runs into locking issues with SQLite because your CPU is many times faster than the mechanical heads of your disks. To some extent, this may also happen with solid-state drives, but it is much more likely with slow storage. +If you use [traditional hard drives instead of SSDs](performance.md#storage), you will find that +Starsky frequently runs into locking issues with SQLite because your CPU is many times faster than +the mechanical heads of your disks. To some extent, this may also happen with solid-state drives, +but it is much more likely with slow storage. -You may be able to optimize the behavior and reduce locking errors with SQLite parameters that you can set with the [database config option](../config-options.md#database-connection), but ultimately you should use an SSD if you want to keep SQLite or switch to MariaDB. Please note that our team cannot provide support otherwise. +You may be able to optimize the behavior and reduce locking errors with SQLite parameters that you +can set with the [database config option](../configuration/config-options.md#database-connection), +but ultimately you should use an SSD if you want to keep SQLite or switch to MariaDB. Please note +that our team cannot provide support otherwise. ## Server Crashes -If the server crashes unexpectedly or your database files get corrupted frequently, it is usually because they are stored on an unreliable device such as a USB flash drive, an SD card, or a shared network folder mounted via NFS or CIFS. These may also have [unexpected file size limitations](https://thegeekpage.com/fix-the-file-size-exceeds-the-limit-allowed-and-cannot-be-saved/), which is especially problematic for databases that do not split data into smaller files. +If the server crashes unexpectedly or your database files get corrupted frequently, it is usually +because they are stored on an unreliable device such as a USB flash drive, an SD card, or a shared +network folder mounted via NFS or CIFS. These may also +have [unexpected file size limitations](https://thegeekpage.com/fix-the-file-size-exceeds-the-limit-allowed-and-cannot-be-saved/), +which is especially problematic for databases that do not split data into smaller files. - [ ] Never use the same database files with more than one server instance - [ ] Use SSDs instead of traditional hard drives, never use network storage